TypeScript - 带有 Sequelize 的存储库模式
TypeScript - Repository pattern with Sequelize
我正在将我的 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
正确注释 return 类型。
所以,我最终这样做了:
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 不会抱怨 findOne
或 create
之类的方法不存在,但这会产生另一个问题。
现在,例如,每当我从存储库中获取 User
时,如果我尝试访问其属性之一,如 user.email
,TypeScript 将抱怨此 属性不存在。当然,因为类型Model
不知道每个型号的具体情况。
好吧,那是 叛国 仿制药。
现在 BaseRepository
使用方法也使用的通用 Model
类型:
export default abstract class BaseRepository<Model> {
public async all(): Promise<Model[]> {
return Model.findAll();
}
// other common methods
}
并且具体 classes 将适当的模型传递给通用类型:
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 正确亮起,它显示了抽象和具体的 classes 方法和模型属性(例如 user.email
)。
但是,如您所想,这会导致更多问题。
在 BaseRepository
内部,方法使用 Model
泛型类型,TypeScript 抱怨 'Model' only refers to a type, but is being used as a value here
。不仅如此,TypeScript 也(再次)不知道模型中的静态方法存在,例如 findAll
、create
等
另一个问题是,在抽象和具体的 classes 中,由于方法不再使用 this
,ESLint 期望方法是静态的:Expected 'this' to be used by class async method 'all'
。好的,我可以在整个文件中忽略这条规则,错误就消失了。如果所有的方法都设置为静态的就更好了,这样我就不用实例化存储库了,但也许我做梦太多了。
值得一提的是,虽然我可以使用 // @ts-ignore
消除这些错误,但当我执行它时,它不起作用:TypeError: Cannot read property 'create' of undefined\n at UserRepository.<anonymous>
我研究了很多,试图让所有的方法都是静态的,但是静态方法不能引用泛型类型(因为它被认为是一个实例属性),尝试了一些变通方法,试图通过具体BaseRepository
的构造函数中的模型以及使用通用类型的 class,但到目前为止似乎没有任何效果。
如果您想查看代码:https://github.com/andresilva-cc/express-api-template/tree/main/src/App/Repositories
编辑:
找到这个:
好的,我从 post 中删除了一些不必要的代码,效果还不错:
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 会抱怨。
我们遇到了同样的问题。解决方案是使用抽象存储库 class 实现的接口声明返回类型。
接口代码:
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>;
};
摘要代码 class:
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));
}
}
}
希望对您有所帮助。祝你有美好的一天:)
编辑:
结果是 class,看起来像这样:
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 class:
type RepoErrorCode = 404 | 500;
export class RepoError extends Error {
public code: RepoErrorCode;
constructor(message: string, code: RepoErrorCode) {
super(message);
this.code = code;
}
}
回购结果类型:
export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
您可以在下面的 link 中找到有关该模式的更多信息:
https://khalilstemmler.com/articles/enterprise-typescript-nodejs/functional-error-handling/
我正在将我的 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
正确注释 return 类型。
所以,我最终这样做了:
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 不会抱怨 findOne
或 create
之类的方法不存在,但这会产生另一个问题。
现在,例如,每当我从存储库中获取 User
时,如果我尝试访问其属性之一,如 user.email
,TypeScript 将抱怨此 属性不存在。当然,因为类型Model
不知道每个型号的具体情况。
好吧,那是 叛国 仿制药。
现在 BaseRepository
使用方法也使用的通用 Model
类型:
export default abstract class BaseRepository<Model> {
public async all(): Promise<Model[]> {
return Model.findAll();
}
// other common methods
}
并且具体 classes 将适当的模型传递给通用类型:
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 正确亮起,它显示了抽象和具体的 classes 方法和模型属性(例如 user.email
)。
但是,如您所想,这会导致更多问题。
在 BaseRepository
内部,方法使用 Model
泛型类型,TypeScript 抱怨 'Model' only refers to a type, but is being used as a value here
。不仅如此,TypeScript 也(再次)不知道模型中的静态方法存在,例如 findAll
、create
等
另一个问题是,在抽象和具体的 classes 中,由于方法不再使用 this
,ESLint 期望方法是静态的:Expected 'this' to be used by class async method 'all'
。好的,我可以在整个文件中忽略这条规则,错误就消失了。如果所有的方法都设置为静态的就更好了,这样我就不用实例化存储库了,但也许我做梦太多了。
值得一提的是,虽然我可以使用 // @ts-ignore
消除这些错误,但当我执行它时,它不起作用:TypeError: Cannot read property 'create' of undefined\n at UserRepository.<anonymous>
我研究了很多,试图让所有的方法都是静态的,但是静态方法不能引用泛型类型(因为它被认为是一个实例属性),尝试了一些变通方法,试图通过具体BaseRepository
的构造函数中的模型以及使用通用类型的 class,但到目前为止似乎没有任何效果。
如果您想查看代码:https://github.com/andresilva-cc/express-api-template/tree/main/src/App/Repositories
编辑:
找到这个:
好的,我从 post 中删除了一些不必要的代码,效果还不错:
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 会抱怨。
我们遇到了同样的问题。解决方案是使用抽象存储库 class 实现的接口声明返回类型。
接口代码:
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>;
};
摘要代码 class:
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));
}
}
}
希望对您有所帮助。祝你有美好的一天:)
编辑: 结果是 class,看起来像这样:
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 class:
type RepoErrorCode = 404 | 500;
export class RepoError extends Error {
public code: RepoErrorCode;
constructor(message: string, code: RepoErrorCode) {
super(message);
this.code = code;
}
}
回购结果类型:
export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
您可以在下面的 link 中找到有关该模式的更多信息: https://khalilstemmler.com/articles/enterprise-typescript-nodejs/functional-error-handling/