使用打字稿在续集模型中创建实例方法



我想扩展一个 Sequelize Model 类以添加其他实例方法,但打字稿一直抱怨"类型'模型'上不存在属性'原型'"

const MyModel = (sequelize: Sequelize.Sequelize, dataTypes: Sequelize.DataTypes) => {
  const User = sequelize.define<Instance, Attribute>(
    "users",
    {
      id: {
        type: dataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true,
      },
      email: {
        type: dataTypes.STRING
      },
      ...
    },
    {
      tableName: "users",
      ...
    },
  );
  User.prototype.verifyUser = function(password: string) {
    ...
  };
  return User;
};

我希望User.prototype.verifyUser可以工作,但打字稿抱怨。如何添加到打字?

在@Shadrech评论之后,我有一个替代方案(不那么笨拙和抽象)。

export interface UserAttributes {
   ...
}
export interface UserInstance extends Sequelize.Instance<UserAttributes>, UserAttributes {
}
interface UserModelInstanceMethods extends Sequelize.Model<UserInstance, UserAttributes> {
  // Came to this question looking for a better approach to this
  // You'll need root's definitions for invocation and prototype's for creation
  verifyPassword: (password: string) => Promise<boolean>;
  prototype: {
    verifyPassword: (password: string) => Promise<boolean>;
  };
}
const MyModel = (sequelize: Sequelize.Sequelize, dataTypes: Sequelize.DataTypes): UserModelInstanceMethods => {
  const User = sequelize.define<UserInstance, UserAttributes>(
      ...
  ) as UserModelInstanceMethods;
  User.prototype.verifyUser = function(password: string) {
    ...
  };
  return User;
}

使用您的模型:

sequelize.query("SELECT ...").then((user: UserInstance & UserModelInstanceMethods) => {
  user.verifyPassword(req.body.password) // <= from UserModelInstanceMethods
  user.getDataValue('name') // <= from UserInstance
})

根据主要的Sequelize TypeScript Doc,我认为实现它的最佳方法是使用DataTypes.VIRTUAL并在模型创建界面上使用TypeScript Omit实用程序跳过该属性。

重要!记住问题#11675!

  • https://stackoverflow.com/a/69289067/717267
  • https://github.com/sequelize/sequelize/issues/11675

一个简单的例子:

import {
  Sequelize,
  Model,
  ModelDefined,
  DataTypes,
  Optional,
  // ...
} from 'sequelize';
interface ProjectAttributes {
  id: number;
  ownerId: number;
  name: string;
  readonly createdAt: Date;
  readonly updatedAt: Date;
  // #region Methods
  
  myMethod(name: string): Promise<void>; // <<<===
  // #endregion
}
interface ProjectCreationAttributes extends Omit< // <<<===
  Optional<
    ProjectAttributes,
    | 'id'
    | 'createdAt'
  >,
  'myMethod' // <<<===
> {}
class Project extends Model<ProjectAttributes, ProjectCreationAttributes>
  implements ProjectAttributes {
  public id: ProjectAttributes['id'];
  public ownerId: ProjectAttributes['ownerId'];
  public name: ProjectAttributes['name'];
  public readonly createdAt: ProjectAttributes['createdAt'];
  public readonly updatedAt: ProjectAttributes['updatedAt'];
  public readonly myMethod: ProjectAttributes['myMethod'] // <<<===
   /**
   * Initialization to fix Sequelize Issue #11675.
   *
   * @see https://stackoverflow.com/questions/66515762/configuring-babel-typescript-for-sequelize-orm-causes-undefined-properties
   * @see https://github.com/sequelize/sequelize/issues/11675
   * @ref #SEQUELIZE-11675
   */
  constructor(values?: TCreationAttributes, options?: BuildOptions) {
    super(values, options);
    // All fields should be here!
    this.id = this.getDataValue('id');
    this.ownerId = this.getDataValue('ownerId');
    this.name = this.getDataValue('name');
    this.createdAt = this.getDataValue('createdAt');
    this.updatedAt = this.getDataValue('updatedAt');
    this.myMethod = async (name) => { // <<<===
      // Implementation example!
      await this.update({
        name,
      });
    };
  }
  // #region Methods
  public toString() {
    return `@${this.name} [${this.ownerId}] #${this.id}`;
  }
  // #endregion
}
Project.init(
  {
    id: {
      type: DataTypes.INTEGER.UNSIGNED,
      autoIncrement: true,
      primaryKey: true,
    },
    ownerId: {
      type: DataTypes.INTEGER.UNSIGNED,
      allowNull: false,
    },
    name: {
      type: new DataTypes.STRING(128),
      allowNull: false,
    },

    myMethod: { // <<<===
      type: DataTypes.VIRTUAL(DataTypes.ABSTRACT),
    }
  },
  {
    sequelize,
    tableName: "projects",
  }
);

我见过的一个解决方案是在声明模型后强制类型。所以

interface UserModelInstanceMethods extends Sequelize.Model<Instance, Attributes> {
  prototype: {
    verifyPassword: (password: string) => Promise<boolean>;
  };
}
const MyModel = (sequelize: Sequelize.Sequelize, dataTypes: Sequelize.DataTypes) => {
  const User = sequelize.define<Instance, Attribute>(
    "users",
    {
      id: {
        type: dataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true,
      },
      email: {
        type: dataTypes.STRING
      },
      ...
    },
    {
      tableName: "users",
      ...
    },
  );
  User.prototype.verifyUser = function(password: string) {
    ...
  };
  return User;
} as Sequelize.Model<Instance, Attributes> & UserModelInstanceMethods;

第 1 步:

定义一个新类型,该类型将描述模型的定义DefinedModel。此外,接收泛型T,以从接口定义的数据库中获取响应。

第 2 步:

创建一个模型实例,解析connection.define返回到我们的DefinedModel

// Step 0: Declarations
const connection: Sequelize = new Sequelize({...});
const modelName: string = '...';
const definition: ModelAttributes = {...};
const options: ModelOptions = {...};
interface MyInterface {...}; // Should describe table data
// Step 1
type DefinedModel<T> = typeof Model & {
  new(values?: object, options?: BuildOptions): T;
}
// Step 2
const model: DefinedModel<Model> = <DefinedModel<Model>>connection.define(modelName, definition, options);
// Step 2 with Interface definition
const iModel: DefinedModel<MyInterface & Model> = <DefinedModel<MyInterface & Model>> connection.define(modelName, definition, options);

最新更新