使用 Typescript 在 Sequelize 模型中创建实例方法

Posted

技术标签:

【中文标题】使用 Typescript 在 Sequelize 模型中创建实例方法【英文标题】:Creating instance methods in a Sequelize Model using Typescript 【发布时间】:2019-06-25 23:57:48 【问题描述】:

我想扩展一个 Sequelize Model 类以添加其他实例方法,但 typescript 一直抱怨“类型 'Model' 上不存在属性 'prototype'”

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 可以工作,但 typescript 会抱怨。如何添加到打字?

【问题讨论】:

【参考方案1】:

根据主要的Sequelize TypeScript Doc,我认为最好的实现方式是在模型创建界面使用DataTypes.VIRTUAL,跳过带有TypeScript Omit utility的属性。

重要!记住Issue#11675!

https://***.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://***.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",
  
);

【讨论】:

【参考方案2】:

第 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);

【讨论】:

【参考方案3】:

在@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
)

【讨论】:

【参考方案4】:

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

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;

【讨论】:

以上是关于使用 Typescript 在 Sequelize 模型中创建实例方法的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 TypeScript 和 Sequelize

使用 TypeScript 在 Node.js 中进行 Sequelize - 没有“new”就无法调用类构造函数模型

TypeScript - 带有 Sequelize 的存储库模式

sequelize/sequelize-typescript - 带有 HasMany 的 findAll 返回一个对象而不是数组

如何使用 sequelize-typescript 从单个字段中引用多个模型?

使用 sequelize 和 typescript 进行自动迁移