TypeScript中MongoDB类型安全查询封装的投影问题及解决方法
TypeScript + MongoDB 类型安全查询封装:实现投影的类型缩小
我正在开发一个基于MongoDB的TypeScript项目,目标是为所有数据库查询构建类型安全的通用封装器。最初通过条件类型实现了集合Schema的匹配,但无法定义能缩小过滤器和函数返回值类型范围的投影逻辑。
2023年12月22日更新:已通过函数重载解决该问题,完整实现代码如下:
import { Filter, MongoClient, WithId } from 'mongodb' // 集合1的数据结构定义 interface Alerts { message: string severity: number } // 集合2的数据结构定义 interface Animals { type: string legs: number } // 根据集合名称匹配对应的数据类型 type CollectionType<T> = T extends { collectionName: 'alerts' } ? Alerts : T extends { collectionName: 'animals' } ? Animals : never // 带_id的完整文档类型 type TSchema<T> = WithId<CollectionType<T>> // 投影对象类型定义 type Projection<T> = { [Property in keyof TSchema<T>]?: 0 | 1 } // 投影字段数组类型 type Fields<T> = (keyof TSchema<T>)[] // 不带投影的查询参数接口 interface GetWithoutProjection<T> { collectionName: 'alerts' | 'animals' filter: Filter<TSchema<T>> } // 带投影的查询参数接口(继承基础参数) interface GetWithProjection<T> extends GetWithoutProjection<T> { project: Fields<T> } // 查询参数联合类型 type GetParams<T> = GetWithProjection<T> | GetWithoutProjection<T> // 函数重载类型定义 type GetOneFunction = { <T extends GetWithProjection<T>>(params: T): Promise<Pick<TSchema<T>, T['project'][number]>> <T extends GetWithoutProjection<T>>(params: T): Promise<TSchema<T>> } const getOne: GetOneFunction = async <T extends GetParams<T>>(params: T) => { const { filter, collectionName } = params const client = await MongoClient.connect('connection-string') const collection = client.db('db-name').collection<TSchema<T>>(collectionName) const projection: Projection<T> = {} if ('project' in params) { projection._id = 0 params.project.forEach(p => { projection[p] = 1 }) } const result = await collection.findOne(filter, { projection }) if (!result) throw new Error(`在集合${collectionName}中未找到匹配${JSON.stringify(filter)}的文档`) return result } export const test = async () => { // 类型为 WithId<Alerts> const alerts1 = await getOne({ collectionName: 'alerts', filter: {} }) console.log(alerts1.severity) // 类型为 WithId<Animals> const animal1 = await getOne({ collectionName: 'animals', filter: {} }) console.log(animal1.type) // 类型为 Pick<WithId<Alerts>, '_id' | 'message'> const alerts2 = await getOne({ collectionName: 'alerts', project: ['_id', 'message'], filter: {} }) console.log(alerts2.severity) // 编译报错:Property 'severity' does not exist on type 'Pick<TSchema<{ collectionName: "alerts"; project: ("_id" | "message")[]; filter: {}; }>, "_id" | "message">'. // 类型为 Pick<WithId<Animals>, '_id' | 'legs'> const animal2 = await getOne({ collectionName: 'animals', project: ['_id', 'legs'], filter: {} }) console.log(animal2.type) // 编译报错:Property 'type' does not exist on type 'Pick<TSchema<{ collectionName: "animals"; project: ("_id" | "legs")[]; filter: {}; }>, "_id" | "legs">' }
实现要点
- 集合Schema自动匹配:通过
CollectionType条件类型,根据collectionName参数自动关联对应集合的数据结构 - 投影的类型约束:利用函数重载区分带/不带投影的调用场景,带投影时通过
Pick工具类型精准推导返回值的字段范围,访问未投影字段会触发编译报错 - 类型安全的查询逻辑:所有参数和返回值都受TypeScript类型校验约束,避免运行时因字段错误导致的问题
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

