如何从GraphQL Schema生成适配REST的扁平化TypeScript类型?
解决方案:从GraphQL Schema生成扁平化REST适配的TypeScript类型
针对你的场景,有几种可行的方案,优先基于你已经在用的graphql-codegen做优化,也可以考虑自定义工具:
一、优化graphql-codegen配置:自定义Schema转换函数
通过@graphql-codegen/typescript插件的transformSchema配置,提前修改GraphQL Schema,移除connection/edges/node层级,再生成类型。
步骤:
- 在项目中创建Schema转换脚本,比如
scripts/transform-schema.ts:
import { GraphQLSchema, GraphQLObjectType, GraphQLList, GraphQLNonNull } from 'graphql'; export default function transformSchema(schema: GraphQLSchema): GraphQLSchema { const typeMap = schema.getTypeMap(); // 处理所有Connection类型,替换为扁平化的列表结构 Object.keys(typeMap).forEach(typeName => { const type = typeMap[typeName]; if (type instanceof GraphQLObjectType && typeName.endsWith('Connection')) { const edgesField = type.getFields().edges; if (edgesField) { let nodeType = edgesField.type; // 解开List/NonNull包装,拿到Edge类型 while (nodeType instanceof GraphQLList || nodeType instanceof GraphQLNonNull) { nodeType = nodeType.ofType; } // 提取Edge类型中的node字段类型 if (nodeType instanceof GraphQLObjectType) { const nodeField = nodeType.getFields().node; if (nodeField) { // 重写Connection类型为包含items列表的结构 typeMap[typeName] = new GraphQLObjectType({ name: typeName, fields: { items: { type: new GraphQLList(nodeField.type) }, // 保留分页信息(如果需要) ...(type.getFields().pageInfo ? { pageInfo: type.getFields().pageInfo } : {}) } }); } } } } // 替换其他类型中引用Connection的字段为扁平化列表 if (type instanceof GraphQLObjectType) { Object.values(type.getFields()).forEach(field => { let innerType = field.type; while (innerType instanceof GraphQLList || innerType instanceof GraphQLNonNull) { innerType = innerType.ofType; } if (innerType instanceof GraphQLObjectType && innerType.name.endsWith('Connection')) { const edgesField = innerType.getFields().edges; if (edgesField) { let edgesInnerType = edgesField.type; while (edgesInnerType instanceof GraphQLList || edgesInnerType instanceof GraphQLNonNull) { edgesInnerType = edgesInnerType.ofType; } if (edgesInnerType instanceof GraphQLObjectType) { const nodeField = edgesInnerType.getFields().node; if (nodeField) { field.type = new GraphQLList(nodeField.type); } } } } }); } }); return new GraphQLSchema({ query: schema.getQueryType(), mutation: schema.getMutationType(), }); }
- 修改
codegen.yml配置,引入这个转换脚本:
schema: "https://你的CMS-GraphQL端点" generates: src/types/rest-types.ts: plugins: - typescript config: transformSchema: ./scripts/transform-schema.ts
执行代码生成命令后,得到的类型会自动把Connection结构转换为items数组,对应REST返回的扁平化列表。
二、编写自定义graphql-codegen插件
如果Schema转换不够灵活,可以直接操作生成的TypeScript AST,移除GraphQL专属层级。
步骤:
- 创建自定义插件脚本
scripts/custom-rest-plugin.ts:
import { PluginFunction, Types } from '@graphql-codegen/plugin-helpers'; import { visit, concatAST, Kind } from 'graphql'; import { TypeScriptDocumentNodesVisitor } from '@graphql-codegen/typescript'; export const plugin: PluginFunction = (schema: Types.IntrospectionResult, documents: Types.DocumentFile[], config: any) => { const visitor = new TypeScriptDocumentNodesVisitor(schema, config); const ast = concatAST(documents.map(doc => doc.document!)); const modifiedAST = visit(ast, { [Kind.OBJECT_TYPE_DEFINITION]: (node) => { // 替换Connection类型为对应的Node列表结构 if (node.name.value.endsWith('Connection')) { const nodeTypeName = node.name.value.replace('Connection', ''); const targetNodeType = schema.__schema.types.find(t => t.name === nodeTypeName); if (targetNodeType) { return { ...node, name: { ...node.name, value: `${nodeTypeName}List` }, fields: targetNodeType.fields?.map(f => ({ ...f })) }; } } return node; }, [Kind.FIELD_DEFINITION]: (node) => { // 把字段中的Connection类型引用替换为Node列表 if (node.type.kind === Kind.NAMED_TYPE && node.type.name.value.endsWith('Connection')) { const nodeTypeName = node.type.name.value.replace('Connection', ''); return { ...node, type: { kind: Kind.LIST_TYPE, type: { kind: Kind.NAMED_TYPE, name: { value: nodeTypeName } } } }; } return node; } }); return visitor.visit(modifiedAST); };
- 在
codegen.yml中引用这个插件:
schema: "https://你的CMS-GraphQL端点" generates: src/types/rest-types.ts: plugins: - ./scripts/custom-rest-plugin.ts
三、手动解析Schema生成类型
如果不想依赖graphql-codegen,可以用graphql和ts-morph手动处理:
- 通过
introspectionQuery获取CMS的GraphQL Schema - 遍历Schema类型,把
Connection/edges/node结构转换为扁平化结构 - 用
ts-morph生成TypeScript类型文件
注意事项
- 要根据你的CMS实际Schema结构调整转换逻辑,比如部分CMS用
nodes代替edges.node,需要对应修改代码 - 生成后要校验类型和REST API返回的结构是否完全匹配,避免字段缺失或冗余
内容的提问来源于stack exchange,提问作者Aliaks
相关产品推荐
相关产品推荐

