Mongoose的预保存方法在Express TypeScript中会引发类型错误。

huangapple go评论106阅读模式
英文:

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();
});

huangapple
  • 本文由 发表于 2023年5月28日 13:19:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/76350053.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定