【问题标题】:TypeScript - Repository pattern with SequelizeTypeScript - 带有 Sequelize 的存储库模式
【发布时间】:2021-11-02 04:24:13
【问题描述】:

我正在将我的 Express API 模板转换为 TypeScript,但我在存储库中遇到了一些问题。

使用 JavaScript,我会做这样的事情:

export default class BaseRepository {
  async all() {
    return this.model.findAll();
  }

  // other common methods
}
import BaseRepository from './BaseRepository';
import { User } from '../Models';

export default class UserRepository extends BaseRepository {
  constructor() {
    super();
    this.model = User;
  }

  async findByEmail(email) {
    return this.model.findOne({
      where: {
        email,
      },
    });
  }

  // other methods

现在,使用 TypeScript,问题在于它不知道 this.model 的类型,我无法将具体模型传递给 BaseRepository,因为它是一个抽象。我发现sequelize-typescript 导出了一个ModelCtor,它声明了所有静态模型方法,如findAll、create 等,我还可以使用另一个sequelize-typescript 导出Model 来正确注释返回类型。

所以,我最终这样做了:

import { Model, ModelCtor } from 'sequelize-typescript';

export default abstract class BaseRepository {
  protected model: ModelCtor;

  constructor(model: ModelCtor) {
    this.model = model;
  }

  public async all(): Promise<Model[]> {
    return this.model.findAll();
  }

  // other common methods
}
import { Model } from 'sequelize-typescript';
import BaseRepository from './BaseRepository';
import { User } from '../Models';

export default class UserRepository extends BaseRepository {
  constructor() {
    super(User);
  }

  public async findByEmail(email: string): Promise<Model | null> {
    return this.model.findOne({
      where: {
        email,
      },
    });
  }

  // other methods
}

好的,这行得通,TypeScript 不会抱怨诸如 findOnecreate 之类的方法不存在,但这会产生另一个问题。

现在,例如,每当我从存储库中获得 User 时,如果我尝试访问其属性之一,例如 user.email,TypeScript 就会抱怨该属性不存在。当然,因为Model这个类型不知道每个型号的具体情况。

好吧,那就是 treason 泛型。

现在 BaseRepository 使用通用的 Model 类型,方法也使用该类型:

export default abstract class BaseRepository<Model> {
  public async all(): Promise<Model[]> {
    return Model.findAll();
  }

  // other common methods
}

具体类将适当的模型传递给泛型类型:

import BaseRepository from './BaseRepository';
import { User } from '../Models';

export default class UserRepository extends BaseRepository<User> {
  public async findByEmail(email: string): Promise<User | null> {
    return User.findOne({
      where: {
        email,
      },
    });
  }

  // other methods
}

现在 IntelliSense 可以正确亮起,它同时显示抽象类和具体类方法以及模型属性(例如 user.email)。

但是,正如您所想象的那样,这会导致更多问题。

BaseRepository 内部,方法使用Model 泛型类型,TypeScript 抱怨'Model' only refers to a type, but is being used as a value here。不仅如此,TypeScript 也(再次)不知道模型中的静态方法存在,例如 findAllcreate 等。

另一个问题是,在抽象类和具体类中,由于方法不再使用this,ESLint 期望方法是静态的:Expected 'this' to be used by class async method 'all'。好的,我可以在整个文件中忽略此规则,错误就消失了。将所有方法设置为静态会更好,因此我不必实例化存储库,但也许我做梦太多了。

值得一提的是,虽然我可以使用 // @ts-ignore 来消除这些错误,但当我执行此操作时,它不起作用:TypeError: Cannot read property 'create' of undefined\n at UserRepository.&lt;anonymous&gt;

我研究了很多,试图让所有方法都是静态的,但是静态方法不能引用泛型类型(因为它被认为是实例属性),尝试了一些变通方法,尝试在@的构造函数中传递具体模型987654352@ 以及使用泛型类型的类,但到目前为止似乎没有任何效果。

如果您想检查代码:https://github.com/andresilva-cc/express-api-template/tree/main/src/App/Repositories

编辑:

找到这个:Sequelize-Typescript typeof model

好的,我从那篇帖子中删除了一些不必要的代码,这有点用:

import { Model } from 'sequelize-typescript';

export default abstract class BaseRepository<M extends Model> {
  constructor(protected model: typeof Model) {}

  public async all(attributes?: string[]): Promise<M[]> {
    // Type 'Model<{}, {}>[]' is not assignable to type 'M[]'.
    // Type 'Model<{}, {}>' is not assignable to type 'M'.
    // 'Model<{}, {}>' is assignable to the constraint of type 'M', but 'M' could be instantiated with a different subtype of constraint 'Model<any, any>'.
    return this.model.findAll({
      attributes,
    });
  }
import BaseRepository from './BaseRepository';
import { User } from '../Models';

export default class UserRepository extends BaseRepository<User> {
  constructor() {
    super(User);
  }
}

我的意思是,如果我输入一些 // @ts-ignore,它至少会执行,并且 IntelliSense 会完美亮起,但 TypeScript 会抱怨。

【问题讨论】:

    标签: node.js typescript sequelize.js typescript-generics sequelize-typescript


    【解决方案1】:

    我们遇到了同样的问题。解决方案是使用抽象存储库类实现的接口声明返回类型。

    接口代码:

    export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
    
    export interface IRepo<M> {
        save(model: M): RepoResult<M>;
        findById(id: string): RepoResult<M>;
        search(parameterName: string, parameterValue: string, sortBy: string, order: number, pageSize: number, pageNumber: number): RepoResult<M[]>;
        getAll(): RepoResult<M[]>;
        deleteById(id: string): RepoResult<M>;
        findByIds(ids: string[]): RepoResult<M[]>;
        deleteByIds(ids: string[]): RepoResult<any>;
    };
    

    抽象类的代码:

    export abstract class Repo<M extends sequelize.Model> implements IRepo<M> {
                protected Model!: sequelize.ModelCtor<M>;
                constructor(Model: sequelize.ModelCtor<M>) {
                    this.Model = Model;
                }
            
                public async save(doc: M) {
                    try {
                        const savedDoc = await doc.save();
                        return Result.ok(savedDoc);
                    } catch (ex: any) {
                        logger.error(ex);
                        return Result.fail(new RepoError(ex.message, 500));
                    }
                }
            
                public async findById(id: string) {
                    try {
                        const doc = await this.Model.findOne({where: {
                            id: id
                        }});
                        if (!doc) {
                            return Result.fail(new RepoError('Not found', 404));
                        }
            
                        return Result.ok(doc);
                    } catch (ex: any) {
                        return Result.fail(new RepoError(ex.message, 500));
                    }
                }
            }
    

    希望对您有所帮助。祝你有美好的一天:)

    编辑: 结果是一个如下所示的类:

    export class Result<V, E> {
    public isSuccess: boolean;
    public isFailure: boolean;
    private error: E;
    private value: V;
    
    private constructor(isSuccess: boolean, value: V, error: E) {
        if (isSuccess && error) {
            throw new Error('Successful result must not contain an error');
        } else if (!isSuccess && value) {
            throw new Error('Unsuccessful error must not contain a value');
        }
        
        this.isSuccess = isSuccess;
        this.isFailure = !isSuccess;
        this.value = value;
        this.error = error;
    }
    
    public static ok<V>(value: V): Result<V, undefined> {
        return new Result(true, value, undefined);
    }
    
    public static fail<E>(error: E): Result<undefined, E> {
        return new Result(false, undefined, error);
    }
    
    public getError(): E {
        if (this.isSuccess) {
            throw new Error('Successful result does not contain an error');
        }
    
        return this.error;
    }
    
    public getValue(): V {
        if (this.isFailure) {
            throw new Error('Unsuccessful result does not contain a value');
        }
    
        return this.value;
    }
    }
    

    RepoError 类:

    type RepoErrorCode = 404 | 500;
    
    export class RepoError extends Error {
        public code: RepoErrorCode;
        constructor(message: string, code: RepoErrorCode) {
            super(message);
            this.code = code;
        }
    }
    

    RepoResult 类型:

    export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
    

    您可以在以下链接中找到有关该模式的更多信息: https://khalilstemmler.com/articles/enterprise-typescript-nodejs/functional-error-handling/

    【讨论】:

    • 这里的repoError和结果是什么?您能否在界面中添加这些导入?
    • 完成!很高兴,如果它有帮助:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-03
    • 2023-03-21
    • 2021-11-15
    • 1970-01-01
    • 2021-07-20
    • 2011-03-09
    • 1970-01-01
    相关资源
    最近更新 更多