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

如何用TypeScript实现支持自动补全的Mongoose接口生成器?

TypeScript实现Mongoose自动补全生成器方案

核心思路

通过泛型追踪实体类型,结合运行时动态挂载模型,让TypeScript在编译时自动推断属性类型,实现完整的自动补全支持。同时解决你之前使用Object.defineProperty未成功的问题,无需手动维护声明文件。

完整实现代码

import mongoose, { Schema, Model, Document } from 'mongoose';

// 类型工具:将Mongoose字段定义转换为TypeScript文档类型
type MongooseFieldToTs<T> = 
  T extends typeof String ? string :
  T extends typeof Number ? number :
  T extends typeof Boolean ? boolean :
  T extends typeof Date ? Date :
  T extends Array<infer U> ? MongooseFieldToTs<U>[] :
  T extends { type: infer U } ? MongooseFieldToTs<U> :
  any;

// 将实体字段映射为文档类型
type EntityToDocument<T extends Record<string, any>> = {
  [K in keyof T]: MongooseFieldToTs<T[K]>;
};

class Database<T extends Record<string, any> = {}> {
  protected entities = new Map<string, Record<string, any>>();
  public models: Partial<{ [K in keyof T]: Model<Document & EntityToDocument<T[K]>> }> = {};

  /**
   * 注册实体
   * @param param 实体名称与字段定义
   * @returns 扩展了实体类型的Database实例
   */
  public entity<K extends string, F extends Record<string, any>>(
    { name, fields }: { name: K; fields: F }
  ): Database<T & { [key in K]: F }> {
    this.entities.set(name, fields);
    return this as Database<T & { [key in K]: F }>;
  }

  /**
   * 生成Mongoose模型并挂载到实例
   * @returns 包含所有模型的Database实例(自动补全生效)
   */
  public generator(): Omit<this, 'entity' | 'generator'> & { 
    [K in keyof T]: Model<Document & EntityToDocument<T[K]>> 
  } {
    // 遍历注册的实体,创建Schema与Model
    this.entities.forEach((fields, entityName) => {
      const schema = new Schema(fields);
      const model = mongoose.model(entityName, schema);

      // 挂载到models对象
      this.models[entityName as keyof T] = model;
      // 直接挂载到Database实例
      Object.defineProperty(this, entityName, {
        get: () => model,
        enumerable: true,
        configurable: true
      });
    });

    // 类型断言让TypeScript识别动态添加的属性
    return this as Omit<this, 'entity' | 'generator'> & { 
      [K in keyof T]: Model<Document & EntityToDocument<T[K]>> 
    };
  }
}

// 使用示例
(async () => {
  // 连接MongoDB
  await mongoose.connect('mongodb://localhost:27017/test');

  // 注册实体并生成模型
  const db = new Database()
    .entity({
      name: 'user',
      fields: {
        name: String,
        age: Number,
        isActive: Boolean,
        createdAt: Date
      }
    })
    .entity({
      name: 'post',
      fields: {
        title: String,
        content: String,
        author: { type: Schema.Types.ObjectId, ref: 'user' },
        tags: [String]
      }
    })
    .generator();

  // 自动补全生效:db.user、db.models.user都能提示Model的方法与文档属性
  const newUser = await db.user.create({
    name: 'Alice',
    age: 25,
    isActive: true,
    createdAt: new Date()
  });

  const posts = await db.models.post.find({ author: newUser._id });
})();

关键细节说明

  1. 泛型类型追踪:

    • Database类使用泛型T存储所有已注册的实体类型,每次调用entity时,泛型会自动扩展,添加新的实体名称与字段类型。
    • EntityToDocument类型工具将Mongoose的字段定义(如String、Number)转换为对应的TypeScript类型(string、number),让文档属性也能获得自动补全。
  2. 运行时模型挂载:

    • 通过Object.defineProperty将生成的Mongoose模型直接挂载到Database实例,同时也挂载到models对象,满足两种使用方式。
    • 配置enumerable: true确保属性能被遍历到,解决你之前可能遇到的属性不可见问题。
  3. 自动补全实现:

    • generator方法返回的类型移除了entity和generator方法(生成后无需再调用),并添加了所有实体对应的模型属性,TypeScript编译时能直接推断出这些属性的存在,从而提供自动补全。

可选:生成单独声明文件

如果你仍需要生成静态声明文件,可以添加以下脚本,在generator方法中调用即可:

import fs from 'fs';

function generateDeclarationFile(entities: Map<string, Record<string, any>>) {
  let content = `import mongoose, { Model, Document } from 'mongoose';\n\n`;
  
  // 定义每个实体的Document接口
  entities.forEach((fields, name) => {
    const docInterfaceName = `${name.charAt(0).toUpperCase()}${name.slice(1)}Document`;
    content += `export interface ${docInterfaceName} {\n`;
    
    Object.entries(fields).forEach(([key, type]) => {
      const tsType = convertMongooseTypeToTs(type);
      content += `  ${key}: ${tsType};\n`;
    });
    
    content += `}\n\n`;
  });
  
  // 定义模型映射接口
  content += `export interface DatabaseModels {\n`;
  entities.forEach((_, name) => {
    const docInterfaceName = `${name.charAt(0).toUpperCase()}${name.slice(1)}Document`;
    content += `  ${name}: Model<Document & ${docInterfaceName}>;\n`;
  });
  content += `}\n\n`;
  
  // 定义生成后的Database类类型
  content += `export declare class GeneratedDatabase {\n`;
  content += `  public models: DatabaseModels;\n`;
  entities.forEach((_, name) => {
    const docInterfaceName = `${name.charAt(0).toUpperCase()}${name.slice(1)}Document`;
    content += `  public ${name}: Model<Document & ${docInterfaceName}>;\n`;
  });
  content += `}\n`;
  
  fs.writeFileSync('./database.d.ts', content);
}

function convertMongooseTypeToTs(mongooseType: any): string {
  if (mongooseType === String) return 'string';
  if (mongooseType === Number) return 'number';
  if (mongooseType === Boolean) return 'boolean';
  if (mongooseType === Date) return 'Date';
  if (Array.isArray(mongooseType)) return `${convertMongooseTypeToTs(mongooseType[0])}[]`;
  if (typeof mongooseType === 'object' && mongooseType.type) {
    if (mongooseType.type === Schema.Types.ObjectId) return 'mongoose.Types.ObjectId';
    return convertMongooseTypeToTs(mongooseType.type);
  }
  return 'any';
}

// 在Database的generator方法中添加:
// generateDeclarationFile(this.entities);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 05:55:00