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

  1. Переименуйте IUser в IUserDocument. Это должно отделить вашу схему от ваших методов экземпляра.
  2. Импортировать Document из мангуста.
  3. Расширьте интерфейс из Document.

Файл модели

  1. Переименуйте все экземпляры IUser в IUserDocument, включая путь к модулю, если вы переименуете файл.
  2. Переименуйте только определение IUserModel в IUser.
  3. Измените то, что расширяет IUser, с IUserDocument, Document на IUserDocument.
  4. Создайте новый интерфейс с именем IUserModel который расширяется от Model<IUser>.
  5. Объявите ваши статические методы в IUserModel.
  6. Измените тип константы User с Model<IUserModel> на IUserModel, поскольку IUserModel теперь расширяет Model<IUser>.
  7. Измените аргумент типа в вызове модели с <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 затем распознает ваши методы и перестанет жаловаться