TypeScript 中的查询助手
¥Query Helpers in TypeScript
查询助手 允许你在 Mongoose 查询上定义自定义辅助程序方法。查询助手使用链接语法使查询更加语义化。
¥Query helpers let you define custom helper methods on Mongoose queries. Query helpers make queries more semantic using chaining syntax.
以下是查询助手如何在 JavaScript 中工作的示例。
¥The following is an example of how query helpers work in JavaScript.
ProjectSchema.query.byName = function(name) {
return this.find({ name: name });
};
const Project = mongoose.model('Project', ProjectSchema);
// Works. Any Project query, whether it be `find()`, `findOne()`,
// `findOneAndUpdate()`, `delete()`, etc. now has a `byName()` helper
Project.find().where('stars').gt(1000).byName('mongoose');
手动输入查询助手
¥Manually Typed Query Helpers
在 TypeScript 中,你可以使用单独的查询助手接口定义查询助手。Mongoose 的 Model 采用 3 个通用参数:
¥In TypeScript, you can define query helpers using a separate query helpers interface.
Mongoose's Model takes 3 generic parameters:
DocTypeTQueryHelpers型¥a
TQueryHelperstypeTMethods型¥a
TMethodstype
第二个通用参数 TQueryHelpers 应该是一个包含每个查询助手的函数签名的接口。下面是使用 byName 查询辅助程序创建 ProjectModel 的示例。
¥The 2nd generic parameter, TQueryHelpers, should be an interface that contains a function signature for each of your query helpers.
Below is an example of creating a ProjectModel with a byName query helper.
import { HydratedDocument, Model, QueryWithHelpers, Schema, model, connect } from 'mongoose';
interface Project {
name?: string;
stars?: number;
}
interface ProjectQueryHelpers {
byName(name: string): QueryWithHelpers<
HydratedDocument<Project>[],
HydratedDocument<Project>,
ProjectQueryHelpers
>
}
type ProjectModelType = Model<Project, ProjectQueryHelpers>;
const ProjectSchema = new Schema<
Project,
Model<Project, ProjectQueryHelpers>,
{},
ProjectQueryHelpers
>({
name: String,
stars: Number
});
ProjectSchema.query.byName = function byName(
this: QueryWithHelpers<any, HydratedDocument<Project>, ProjectQueryHelpers>,
name: string
) {
return this.find({ name: name });
};
// 2nd param to `model()` is the Model class to return.
const ProjectModel = model<Project, ProjectModelType>('Project', ProjectSchema);
run().catch(err => console.log(err));
async function run(): Promise<void> {
await connect('mongodb://127.0.0.1:27017/test');
// Equivalent to `ProjectModel.find({ stars: { $gt: 1000 }, name: 'mongoose' })`
await ProjectModel.find().where('stars').gt(1000).byName('mongoose');
}
自动键入查询助手
¥Auto Typed Query Helpers
Mongoose 确实支持结构选项中提供的自动类型查询助手。查询助手函数可以定义如下:
¥Mongoose does support auto typed Query Helpers that it are supplied in schema options. Query Helpers functions can be defined as following:
import { Schema, model } from 'mongoose';
const ProjectSchema = new Schema({
name: String,
stars: Number
}, {
query: {
byName(name: string) {
return this.find({ name });
}
}
});
const ProjectModel = model('Project', ProjectSchema);
// Equivalent to `ProjectModel.find({ stars: { $gt: 1000 }, name: 'mongoose' })`
await ProjectModel.find().where('stars').gt(1000).byName('mongoose');
使用查询助手覆盖实现不同的查询结构
¥Using Query Helper Overrides For Different Query Shapes
有时,你希望查询助手根据查询是否为 "lean" 类型返回不同的类型。例如,假设你需要一个 toMap() 查询助手,它将查询结果转换为以 _id 为键的 Map 类型。如果调用 .lean(),你希望映射值是普通对象;否则,你需要的是水合文档。
¥Sometimes you want a query helper to return a different type depending on whether the query is "lean" or not.
For example, suppose you want a toMap() query helper that converts the results of a query into a Map keyed by _id.
If you call .lean(), you want the map values to be plain objects; otherwise, you want hydrated documents.
为了实现这一点,你可以根据 this 的值在查询助手上使用 TypeScript 函数重载。以下示例展示了如何定义 toMap() 查询助手的类型,以便它能够为精简查询和非精简查询返回正确的类型:
¥To achieve this, you can use TypeScript function overloads on your query helper based on the value of this.
Here's an example of how to type a toMap() query helper so that it returns the correct type for both lean and non-lean queries:
import { Model, HydratedDocument, QueryWithHelpers, Schema, model, Types } from 'mongoose';
// Query helper interface with overloads for lean and non-lean queries
export interface ToMapQueryHelpers<RawDocType, HydratedDocType> {
// For non-lean queries: returns Map<string, HydratedDocType>
toMap(this: QueryWithHelpers<HydratedDocType[], HydratedDocType>): QueryWithHelpers<Map<string, HydratedDocType>, HydratedDocType>;
// For lean queries: returns Map<string, RawDocType>
toMap(this: QueryWithHelpers<RawDocType[], HydratedDocType>): QueryWithHelpers<Map<string, RawDocType>, HydratedDocType>;
}
// Query helpers definition. Will be used in schema options
const query: ToMapQueryHelpers<IUser, UserHydratedDocument> = {
// Chainable query helper that converts an array of documents to
// a map of document _id (as a string) to the document
toMap() {
return this.transform((docs) => {
// The `if` statements are type gymnastics to help TypeScript
// handle the `IUser[] | UserHydratedDocument[]` union. Not necessary
// for runtime correctness.
if (docs.length === 0) return new Map();
if (docs[0] instanceof Document) return new Map(docs.map(doc => [doc._id.toString(), doc]));
return new Map(docs.map(doc => [doc._id.toString(), doc]));
});
}
};
export interface IUser {
_id: Types.ObjectId;
name: string;
}
export type UserHydratedDocument = HydratedDocument<IUser>;
export type UserModelType = Model<
IUser,
ToMapQueryHelpers<IUser, UserHydratedDocument>
>;
const userSchema = new Schema({ name: String }, { query });
const User = model<IUser, UserModelType>('User', userSchema);
async function run() {
// Non-lean: Map<string, UserHydratedDocument>
const hydratedMap = await User.find().toMap();
// hydratedMap.get('someId') is a hydrated document
// Lean: Map<string, IUser>
const leanMap = await User.find().lean().toMap();
// leanMap.get('someId') is a plain object
// The following will fail at compile time, as expected, because `toMap()` shouldn't work with single documents or numbers
// await User.findOne().toMap();
// await User.countDocuments().toMap();
}使用这种方法,TypeScript 将根据你是否使用 .lean() 推断 .toMap() 的正确返回类型。这确保了类型安全,并防止在不返回文档数组的查询中意外滥用查询助手。
¥With this approach, TypeScript will infer the correct return type for .toMap() depending on whether you use .lean() or not. This ensures type safety and prevents accidental misuse of the query helper on queries that don't return arrays of documents.
