Typescript метод статической модели mongoose "Свойство не существует по типу"
В настоящее время я пытаюсь добавить статический метод в мою схему mongoose, но я не могу найти причину, почему он не работает таким образом.
Моя модель:
import * as bcrypt from 'bcryptjs';
import { Document, Schema, Model, model } from 'mongoose';
import { IUser } from '../interfaces/IUser';
export interface IUserModel extends IUser, Document {
comparePassword(password: string): boolean;
}
export const userSchema: Schema = new Schema({
email: { type: String, index: { unique: true }, required: true },
name: { type: String, index: { unique: true }, required: true },
password: { type: String, required: true }
});
userSchema.method('comparePassword', function (password: string): boolean {
if (bcrypt.compareSync(password, this.password)) return true;
return false;
});
userSchema.static('hashPassword', (password: string): string => {
return bcrypt.hashSync(password);
});
export const User: Model<IUserModel> = model<IUserModel>('User', userSchema);
export default User;
IUser:
export interface IUser {
email: string;
name: string;
password: string;
}
Если я попытаюсь позвонить User.hashPassword(password)
, я получаю следующую ошибку [ts] Property 'hashPassword' does not exist on type 'Model<IUserModel>'.
Я знаю, что нигде не определял метод, но я действительно не знаю, где бы я мог это выразить, поскольку я не могу просто поставить статический метод в интерфейс.
Надеюсь, вы сможете помочь мне найти ошибку, спасибо заранее!
Ответы
Ответ 1
Я думаю, что у тебя такая же проблема, с которой я просто боролся. Эта проблема в вашем звонке. В нескольких учебниках вы вызываете метод .comparePassword()
из такой модели.
User.comparePassword(candidate, cb...)
Это не работает, потому что метод находится на schema
, а не на model
. Единственный способ, которым я смог вызвать этот метод, - найти этот экземпляр модели, используя стандартные методы запросов mongoose/mongo.
Вот важная часть моего промежуточного программного обеспечения паспорта:
passport.use(
new LocalStrategy({
usernameField: 'email'
},
function (email: string, password: string, done: any) {
User.findOne({ email: email }, function (err: Error, user: IUserModel) {
if (err) throw err;
if (!user) return done(null, false, { msg: 'unknown User' });
user.schema.methods.comparePassword(password, user.password, function (error: Error, isMatch: boolean) {
if (error) throw error;
if (!isMatch) return done(null, false, { msg: 'Invalid password' });
else {
console.log('it was a match'); // lost my $HÏT when I saw it
return done(null, user);
}
})
})
})
);
Итак, я использовал findOne({})
, чтобы получить экземпляр документа, а затем должен был получить доступ к методам схемы, перекопав в свойства схемы в документе user.schema.methods.comparePassword
Несколько различий, которые я заметил:
- Mine - это метод
instance
, а ваш метод static
. Я уверен, что существует аналогичная стратегия доступа к методу.
- Я обнаружил, что мне нужно передать хэш функции
comparePassword()
. возможно, это не обязательно для статики, но мне не удалось получить доступ к this.password
Ответ 2
У меня была та же проблема, что и у вас, и, наконец, мне удалось ее решить после прочтения документации по наборам TS mongoose (о которой я раньше не знал, и я не уверен, сколько времени было в документации), конкретно этот раздел.
Что касается вашего случая, вы захотите следовать той же схеме, что и в настоящее время, хотя вам нужно изменить несколько вещей в обоих файлах.
IUser file
- Переименуйте
IUser
в IUserDocument
. Это должно отделить вашу схему от ваших методов экземпляра. - Импортировать
Document
из мангуста. - Расширьте интерфейс из
Document
.
Файл модели
- Переименуйте все экземпляры
IUser
в IUserDocument
, включая путь к модулю, если вы переименуете файл. - Переименуйте только определение
IUserModel
в IUser
. - Измените то, что расширяет
IUser
, с IUserDocument, Document
на IUserDocument
. - Создайте новый интерфейс с именем
IUserModel
который расширяется от Model<IUser>
. - Объявите ваши статические методы в
IUserModel
. - Измените тип константы
User
с Model<IUserModel>
на IUserModel
, поскольку IUserModel
теперь расширяет Model<IUser>
. - Измените аргумент типа в вызове модели с
<IUserModel>
на <IUser, IUserModel>
.
Вот как будет выглядеть ваш файл модели с этими изменениями:
import * as bcrypt from 'bcryptjs';
import { Document, Schema, Model, model } from 'mongoose';
import { IUserDocument } from '../interfaces/IUserDocument';
export interface IUser extends IUserDocument {
comparePassword(password: string): boolean;
}
export interface IUserModel extends Model<IUser> {
hashPassword(password: string): string;
}
export const userSchema: Schema = new Schema({
email: { type: String, index: { unique: true }, required: true },
name: { type: String, index: { unique: true }, required: true },
password: { type: String, required: true }
});
userSchema.method('comparePassword', function (password: string): boolean {
if (bcrypt.compareSync(password, this.password)) return true;
return false;
});
userSchema.static('hashPassword', (password: string): string => {
return bcrypt.hashSync(password);
});
export const User: IUserModel = model<IUser, IUserModel>('User', userSchema);
export default User;
И ваш (недавно переименованный) ../interfaces/IUserDocument
модуль будет выглядеть так:
import { Document } from 'mongoose';
export interface IUserDocument extends Document {
email: string;
name: string;
password: string;
}
Ответ 3
Для будущих читателей:
Помните, что мы имеем дело с двумя различными понятиями Mongo/Mongoose: Модель и Документы.
Многие документы могут быть созданы из одной модели. Модель - это проект, Документ - это предмет, созданный в соответствии с инструкциями для Модели.
Каждый документ содержит свои собственные данные. Каждый также имеет свои собственные индивидуальные методы экземпляра, которые привязаны к своему собственному this
и работают только с этим одним конкретным экземпляром.
Модель может иметь "статические" методы, которые не привязаны к конкретному экземпляру Document, но работают со всей коллекцией Document.
Как все это относится к TypeScript:
- Расширьте Document, чтобы определить типы для свойств экземпляра и функций
.method
. - Расширьте модель (документа), чтобы определить типы для функций
.static
.
Другие ответы здесь имеют приличный код, поэтому посмотрите на них и проследите разницу между тем, как определяются документы и как определяются модели.
И помните, когда вы собираетесь использовать эти вещи в своем коде, Модель используется для создания новых Документов и для вызова статических методов, таких как User.findOne
или вашей пользовательской статики (например, User.hashPassword
, определенный выше).
И Документы - это то, что вы используете для доступа к конкретным данным из объекта или для вызова методов экземпляра, таких как this.save
и пользовательских методов экземпляра, таких как this.comparePassword
определенных выше.
Ответ 4
Я не вижу ваш интерфейс IUser, однако я подозреваю, что вы не включили в него методы.
EG
export interface IUser {
email: string,
hash: string,
salt: string,
setPassword(password: string): void,
validPassword(password: string): boolean,
generateJwt(): string
}
typescript затем распознает ваши методы и перестанет жаловаться