You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从GraphQL Schema生成适配REST的扁平化TypeScript类型?

解决方案:从GraphQL Schema生成扁平化REST适配的TypeScript类型

针对你的场景,有几种可行的方案,优先基于你已经在用的graphql-codegen做优化,也可以考虑自定义工具:

一、优化graphql-codegen配置:自定义Schema转换函数

通过@graphql-codegen/typescript插件的transformSchema配置,提前修改GraphQL Schema,移除connection/edges/node层级,再生成类型。

步骤:

  1. 在项目中创建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(),
  });
}
  1. 修改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专属层级。

步骤:

  1. 创建自定义插件脚本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);
};
  1. 在codegen.yml中引用这个插件:
schema: "https://你的CMS-GraphQL端点"
generates:
  src/types/rest-types.ts:
    plugins:
      - ./scripts/custom-rest-plugin.ts

三、手动解析Schema生成类型

如果不想依赖graphql-codegen,可以用graphql和ts-morph手动处理:

  1. 通过introspectionQuery获取CMS的GraphQL Schema
  2. 遍历Schema类型,把Connection/edges/node结构转换为扁平化结构
  3. 用ts-morph生成TypeScript类型文件

注意事项

  • 要根据你的CMS实际Schema结构调整转换逻辑,比如部分CMS用nodes代替edges.node,需要对应修改代码
  • 生成后要校验类型和REST API返回的结构是否完全匹配,避免字段缺失或冗余

内容的提问来源于stack exchange,提问作者Aliaks

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 10:05:23