mongoosetypescriptpassport.jsdefinitelytypedmongoose-plugins

Header files for Mongoose' 'plugin' method (extending via .methods and .statics)


I'm trying to create Typescript header files for a script that extends my Mongoose-model by using the .plugin method. Current signature from Mongoose header-files:

export class Schema {
   // ...
   plugin(plugin: (schema: Schema, options?: Object) => void, 
                   options?: Object): Schema;
   // ...
}

Some actual code from Mongoose-lib:

/**
 * Registers a plugin for this schema.
 *
 * @param {Function} plugin callback
 * @param {Object} [opts]
 * @see plugins
 * @api public
 */

Schema.prototype.plugin = function (fn, opts) {
  fn(this, opts);
  return this;
};

Then my own model, extending the plugin;

import passportLocalMongoose = require('passport-local-mongoose')
// ...
var userSchema = new mongoose.Schema({
    email: String,
    password: String,
});
// ...
userSchema.plugin(passportLocalMongoose, {
    usernameField: "email",
    usernameLowerCase: true
});

Snippet from passport-local-mongoose source:

module.exports = function(schema, options) {
   // ...   
   schema.methods.setPassword = function (password, cb) {
      // ...
   }

   schema.statics.authenticate = function() {
      // ...
   }
   // ...
}

Problem occurs in my main app.js

   // ...

   userSchema.authenticate()                // <<< Typescript error, undefined

   //   OR

   userSchemaInstance.setPassword(pass, cb) // <<< Typescript error, undefined

The problem is that .authenticate etc. were dynamically added through .methods and .statics ...

I can't find a way to model this in the typescript header files.

I tried generics and stuff, but I can't (dynamic) apply the provide plugin-methods back to the original model. I also tried plugin returning generic T extends S & P (where S extends Schema from first argument and P = plugin itself). No luck :-(

Any suggestions or examples how to solve this?


Solution

  • Declare interfaces in passport-local-mongoose.d.ts file:

    declare module 'mongoose' {
        // methods
        export interface PassportLocalDocument extends Document {
            setPassword(pass: string, cb: (err: any) => void);
        }
    
        // statics
        export interface PassportLocalModel<T extends PassportLocalDocument> extends Model<T> {
            authenticate(username: string, password: string, cb: (err: any) => void);
        }
    
        // plugin options
        export interface PassportLocalOptions {
            usernameField?: string;
            usernameLowerCase?: boolean;
        }
    
        export interface PassportLocalSchema extends Schema {
            plugin(
                plugin: (schema: PassportLocalSchema, options?: PassportLocalOptions) => void,
                options?: PassportLocalOptions): Schema;
        }
    
        export function model<T extends PassportLocalDocument>(
            name: string,
            schema?: PassportLocalSchema,
            collection?: string,
            skipInit?: boolean): PassportLocalModel<T>;
    }
    
    declare module 'passport-local-mongoose' {
        import mongoose = require('mongoose');
        var _: (schema: mongoose.Schema, Options?: Object) => void;
        export = _;
    }
    

    Use it in your app.ts:

    import mongoose = require('mongoose');
    import passportLocalMongoose = require('passport-local-mongoose');
    
    interface UserDocument extends mongoose.PassportLocalDocument {
        email: string,
        password: string;
    }
    
    var userSchema = <mongoose.PassportLocalSchema>new mongoose.Schema({
        email: String,
        password: String
    });
    
    userSchema.plugin(passportLocalMongoose, {
        usernameField: 'email',
        usernameLowerCase: true
    });
    
    var User = mongoose.model<UserDocument>('User', userSchema);
    User.authenticate(userName, pass, cb);
    
    var user = new User();
    user.setPassword(newPass, cb);