英文:
Mongoose pre save method gives a type error in express typescript
问题
在vscode中,我在以下行中遇到了错误:user.find({ active: { $ne: false } });
错误消息如下:
Property 'find' does not exist on type '(Document<unknown, {}, FlatRecord<IUser>> & Omit<FlatRecord<IUser> & { _id: ObjectId; }, keyof IUserMethods> & IUserMethods) | Query<...>'.
Property 'find' does not exist on type 'Document<unknown, {}, FlatRecord<IUser>> & Omit<FlatRecord<IUser> & { _id: ObjectId; }, keyof IUserMethods> & IUserMethods'.ts(2339)
无法确定问题所在,为什么会说Document<unknown
。
有人可以指引我正确的方向吗?
英文:
My stack is express js in typescript with mongoose. Here is my model:
import mongoose, { Schema, Document, Model } from 'mongoose';
import crypto from 'crypto';
import validator from 'validator';
import bcrypt from 'bcryptjs';
import roles from '../constant/role';
export interface IUser extends Document {
firstName: string;
lastName: string;
phone: string;
email: string;
password: string;
role: string;
dateOfBirth?: Date;
emailVerified?: boolean;
phoneVerified?: boolean;
updatedBy?: Schema.Types.ObjectId;
createdBy?: Schema.Types.ObjectId;
passwordChangedAt?: Date;
passwordResetToken?: string;
passwordResetExpires?: Date;
emailVerificationToken?: string;
emailVerificationExpires?: Date;
active?: boolean;
fullName?: string;
}
export interface IUserMethods {
correctPassword(
candidatePassword: string,
userPassword: string
): Promise<boolean>;
changedPasswordAfter(jwtTimeStamp: number): boolean;
createPasswordResetToken(): string;
createEmailVerificationToken(): string;
}
type UserModel = Model<IUser, {}, IUserMethods>;
const UserSchema = new Schema<IUser, UserModel, IUserMethods>(
{
firstName: {
type: String,
required: [true, 'First Name is required'],
trim: true,
},
lastName: { type: String, trim: true },
phone: {
type: String,
trim: true,
unique: true,
required: [true, 'Phone is required'],
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
validate: [validator.isEmail, 'Email is not valid'],
trim: true,
},
password: {
type: String,
required: [true, 'Password is required'],
select: false,
},
dateOfBirth: { type: Date },
emailVerified: { type: Boolean, default: false },
phoneVerified: { type: Boolean, default: false },
role: {
type: String,
enum: [roles.SUPER_ADMIN, roles.ADMIN, roles.STUDENT, roles.GUEST],
default: roles.GUEST,
},
updatedBy: { type: Schema.Types.ObjectId, ref: 'User' },
createdBy: { type: Schema.Types.ObjectId, ref: 'User' },
passwordChangedAt: { type: Date },
passwordResetToken: String,
passwordResetExpires: Date,
emailVerificationToken: String,
emailVerificationExpires: Date,
active: {
type: Boolean,
default: true,
select: false,
},
},
{
timestamps: true,
versionKey: false,
}
);
UserSchema.pre(/^find/, async function (next) {
const user = this;
user.find({ active: { $ne: false } });
next();
});
const User = mongoose.model<IUser, UserModel>('User', UserSchema);
export default User;
In vscode, I am getting this error in the line: user.find({ active: { $ne: false } });
Property 'find' does not exist on type '(Document<unknown, {}, FlatRecord<IUser>> & Omit<FlatRecord<IUser> & { _id: ObjectId; }, keyof IUserMethods> & IUserMethods) | Query<...>'.
Property 'find' does not exist on type 'Document<unknown, {}, FlatRecord<IUser>> & Omit<FlatRecord<IUser> & { _id: ObjectId; }, keyof IUserMethods> & IUserMethods'.ts(2339)
Not able to figure out what is the issue here, and why does it say Document<unknown
.
Can someone please point me in the right direction?
答案1
得分: 1
您有三种选择:
1. 将 Query<any, any>
作为 schema.pre()
的泛型参数传递
让我们来查看 schema.pre()
方法的 TS 重载签名:
// this = Document 和 Query 的联合类型,可以用任何一个调用
pre<T = THydratedDocumentType|Query<any, any>>(method: MongooseQueryOrDocumentMiddleware | MongooseQueryOrDocumentMiddleware[] | RegExp, fn: PreMiddlewareFunction<T>): this;
以及泛型类型 PreMiddlewareFunction<T>
:
type PreMiddlewareFunction<ThisType = any> = (this: ThisType, next: CallbackWithoutResultAndOptionalError) => void | Promise<void>;
所以,我们可以将泛型参数传递给 schema.pre()
方法,而不是使用类型转换(as
)。这个泛型参数类型将是 this
的类型。
例如.("mongoose": "^7.3.2")
import { Document, Query, Schema } from 'mongoose';
interface IUser extends Document {
firstName: string;
}
const UserSchema = new Schema<IUser>({
firstName: String,
});
UserSchema.pre<Query<any, any>>(/^find/, async function (next) {
const user = this;
user.find({ active: { $ne: false } });
next();
});
2. 明确指定各种 find*
方法
UserSchema.pre(['find', 'findOne', 'findOneAndDelete', 'findOneAndUpdate'], async function (next) {
const user = this;
user.find({ active: { $ne: false } });
next();
});
这样,this
的类型可以推断为 Query
类型。
3. 使用 instanceof
缩小
当将正则表达式传递给 schema.pre()
时,TS 将匹配以下重载签名:
// this = Document 和 Query 的联合类型,可以用任何一个调用
pre<T = THydratedDocumentType|Query<any, any>>(method: MongooseQueryOrDocumentMiddleware | MongooseQueryOrDocumentMiddleware[] | RegExp, fn: PreMiddlewareFunction<T>): this;
this
的类型是一个联合类型:THydratedDocumentType|Query<any, any>
。在 THydratedDocumentType
类型上没有 .find()
方法,这就是您收到错误的原因。
我们可以使用 instanceof
缩小 将 this
的类型缩小为 Query<any, any>
类型,就像这样:
import { Document, Query, Schema } from 'mongoose';
UserSchema.pre(/^find/, async function (next) {
if (this instanceof Query) {
const user = this;
user.find({ active: { $ne: false } });
}
next();
});
英文:
You have three choices:
1. Passing the Query<any, any>
as generic parameter for schema.pre()
Let's check the TS overload signature of the schema.pre()
method:
// this = Union of Document and Query, could be called with any of them
pre<T = THydratedDocumentType|Query<any, any>>(method: MongooseQueryOrDocumentMiddleware | MongooseQueryOrDocumentMiddleware[] | RegExp, fn: PreMiddlewareFunction<T>): this;
And the generic type PreMiddlewareFunction<T>
:
type PreMiddlewareFunction<ThisType = any> = (this: ThisType, next: CallbackWithoutResultAndOptionalError) => void | Promise<void>;
So, instead of using type casting(as
), we can pass the generic parameter to schema.pre()
method. This generic parameter type will be the type of this
.
E.g.("mongoose": "^7.3.2")
import { Document, Query, Schema } from 'mongoose';
interface IUser extends Document {
firstName: string;
}
const UserSchema = new Schema<IUser>({
firstName: String,
});
UserSchema.pre<Query<any, any>>(/^find/, async function (next) {
const user = this;
user.find({ active: { $ne: false } });
next();
});
2. Explicitly specify various find*
methods
UserSchema.pre(['find', 'findOne', 'findOneAndDelete', 'findOneAndUpdate'], async function (next) {
const user = this;
user.find({ active: { $ne: false } });
next();
});
In this way, the type of this
can be inferred as a Query
type.
3. Using instanceof
narrowing
When passing the regular expression to schema.pre()
, TS will match below overload signature:
// this = Union of Document and Query, could be called with any of them
pre<T = THydratedDocumentType|Query<any, any>>(method: MongooseQueryOrDocumentMiddleware | MongooseQueryOrDocumentMiddleware[] | RegExp, fn: PreMiddlewareFunction<T>): this;
The type of this
is a union type: THydratedDocumentType|Query<any, any>
. And there is no .find()
method on the THydratedDocumentType
type, that's why you got the error.
We can narrow the type of this
to Query<any, any>
type using instanceof
narrowing like this:
import { Document, Query, Schema } from 'mongoose';
UserSchema.pre(/^find/, async function (next) {
if (this instanceof Query) {
const user = this;
user.find({ active: { $ne: false } });
}
next();
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论