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); })();
关键说明
JoinParams<HydrateConfig>:递归遍历关联配置的每个字段,每个字段包含对应的model和可选的hydrate子配置,子配置同样遵循JoinParams的递归结构。JoinResult<HydrateConfig, Entity>:将原实体类型与各级关联字段合并,每个关联字段对应子实体的数组,子实体又会递归包含其自身的关联字段。find方法的泛型约束:通过X extends Record<string, { entity: IDocumentModel; hydrate?: any }>让TypeScript自动推断传入的hydrate结构中每一层的实体类型和子关联配置。
这样修改后,IDE就能正确识别所有层级的关联字段,支持任意深度的嵌套关联。
内容的提问来源于stack exchange,提问作者Jonas Müller
相关产品推荐
相关产品推荐

