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

TypeScript中基于递归输入自动推断ORM find方法输出类型

解决TypeScript图数据库ORM中find方法多级关联的类型推断问题

我正在为图数据库编写TypeScript版ORM,核心目标是实现find方法:该方法返回指定实体列表,同时支持传入数据库级联关联结构,自动推断关联字段的类型,方便客户端访问。单级嵌套的实现已完成,但扩展到任意多级嵌套时,IDE无法识别关联字段的类型(仅能补全父实体字段,无法识别子级、孙级关联字段)。

单级嵌套可行代码

interface IDocumentModel {
  _id?: string;
}

type JoinParams<T extends Record<string, IDocumentModel>> = {
  [K in keyof T]: {
    model: DocumentModel<T[K]>;
  };
};

type JoinResult<T, U> = (U & {
  [K in keyof T]: T[K][];
})[];

class DocumentModel<T extends IDocumentModel> {
  async find<X extends Record<string, IDocumentModel>>(
    filter?: Partial<T>,
    hydrate?: JoinParams<X>,
  ): Promise<JoinResult<X, T>> {
    // TODO: implementation
  }
}

const ParentModel = new DocumentModel<{ _id?: string, parentField: string }>();
const ChildModel = new DocumentModel<{ _id?: string, childField: string }>();

const results = await ParentModel.find(
  { _id: 'abc' },
  {
    children: {
      model: ChildModel,
    },
  },
);

console.log(results[0].parentField);
console.log(results[0].children[0].childField);

当前尝试的两级嵌套代码(存在类型推断问题)

interface IDocumentModel {
  _id?: string;
}

type JoinParams<
  T extends
    | Record<string, IDocumentModel>
    | Record<string, Record<string, IDocumentModel>>,
> = {
  [K in keyof T]: {
    model: T extends Record<string, Record<string, IDocumentModel>>
      ? DocumentModel<T[K]['parent']>
      : T extends Record<string, IDocumentModel>
      ? DocumentModel<T[K]>
      : never;
    hydrate?: T extends Record<string, Record<string, IDocumentModel>>
      ? JoinParams<Omit<T[K], 'parent'>>
      : never;
  };
};

type JoinResult<
  T extends
    | Record<string, IDocumentModel>
    | Record<string, Record<string, IDocumentModel>>,
  U,
> = (U & {
  [K in keyof T]: T extends Record<string, Record<string, IDocumentModel>>
    ? JoinResult<Omit<T[K], 'parent'>, T[K]['parent']>
    : T extends Record<string, IDocumentModel>
    ? T[K][]
    : never;
})[];

class DocumentModel<T extends IDocumentModel> {
  async find<X extends Record<string, Record<string, IDocumentModel>>>(
    filter?: Partial<T>,
    hydrate?: JoinParams<X>,
  ): Promise<JoinResult<X, T>> {
    // TODO: implementation
  }
}

const ParentModel = new DocumentModel<{ _id?: string, parentField: string }>();
const ChildModel = new DocumentModel<{ _id?: string, childField: string }>();
const GrandChildModel = new DocumentModel<{ _id?: string, grandChildField: string }>();

const results = await ParentModel.find(
  { _id: 'abc' },
  {
    children: {
      model: ChildModel,
      hydrate: {
        grandchildren: {
          model: GrandChildModel,
        },
      },
    },
  },
);

console.log(results[0].parentField);
console.log(results[0].children[0].childField);
console.log(results[0].children[0].grandchildren[0].grandChildField);

测试时IDE仅能补全results[0].parentField,无法识别results[0].children等关联字段。


解决方案:递归泛型实现任意层级关联的类型推断

问题根源在于原类型定义仅支持固定两级嵌套,无法递归处理任意层级。我们需要将JoinParams和JoinResult改为递归泛型,让TypeScript能自动推断每一层的关联类型。

修正后的完整代码

interface IDocumentModel {
  _id?: string;
}

// 递归定义关联参数:每个字段包含model和可选的子关联hydrate
type JoinParams<HydrateConfig> = {
  [K in keyof HydrateConfig]: {
    model: DocumentModel<HydrateConfig[K]['entity']>;
    hydrate?: JoinParams<HydrateConfig[K]['hydrate']>;
  };
};

// 递归生成返回类型:合并原实体与各级关联字段
type JoinResult<HydrateConfig, Entity> = (Entity & {
  [K in keyof HydrateConfig]: JoinResult<
    HydrateConfig[K]['hydrate'],
    HydrateConfig[K]['entity']
  >[];
})[];

class DocumentModel<T extends IDocumentModel> {
  // 泛型X接收关联配置,通过结构自动推断每层的entity和hydrate
  async find<X extends Record<string, { entity: IDocumentModel; hydrate?: any }>>(
    filter?: Partial<T>,
    hydrate?: JoinParams<X>,
  ): Promise<JoinResult<X, T>> {
    // TODO: 实现数据库查询和级联关联逻辑
    return [] as any;
  }
}

// 测试用例
const ParentModel = new DocumentModel<{ _id?: string; parentField: string }>();
const ChildModel = new DocumentModel<{ _id?: string; childField: string }>();
const GrandChildModel = new DocumentModel<{ _id?: string; grandChildField: string }>();

(async () => {
  const results = await ParentModel.find(
    { _id: 'abc' },
    {
      children: {
        model: ChildModel,
        hydrate: {
          grandchildren: {
            model: GrandChildModel,
            // 支持无限层级嵌套
            // hydrate: {
            //   greatGrandchildren: { model: GreatGrandChildModel }
            // }
          },
        },
      },
    },
  );

  // 现在IDE可以正确补全所有层级的字段
  console.log(results[0].parentField);
  console.log(results[0].children[0].childField);
  console.log(results[0].children[0].grandchildren[0].grandChildField);
})();

关键说明

  1. JoinParams<HydrateConfig>:递归遍历关联配置的每个字段,每个字段包含对应的model和可选的hydrate子配置,子配置同样遵循JoinParams的递归结构。
  2. JoinResult<HydrateConfig, Entity>:将原实体类型与各级关联字段合并,每个关联字段对应子实体的数组,子实体又会递归包含其自身的关联字段。
  3. find方法的泛型约束:通过X extends Record<string, { entity: IDocumentModel; hydrate?: any }>让TypeScript自动推断传入的hydrate结构中每一层的实体类型和子关联配置。

这样修改后,IDE就能正确识别所有层级的关联字段,支持任意深度的嵌套关联。


内容的提问来源于stack exchange,提问作者Jonas Müller

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 16:02:10