Implement a mode, which separates static operations from models. So that User.find will not be possible in this mode. A repository need to be created instead like
const sequelize = new Sequelize({repositoryMode: true, ...})
const userRepository = sequelize.getRepository(User);
userRepository.find(...);
This should separate the initialization of a model to the repository model as well.
@snewell92 @bilby91 any thoughts on this? :)
@RobinBuschmann I really like the idea of decoupling the model from the data access. In the project I'm working on in node at the moment I'm using this pattern.
This is my base repository:
// Import need to use * https://github.com/types/sequelize/issues/20
import { InstanceUpdateOptions, WhereOptions } from "sequelize"
import * as Sequelize from "sequelize-typescript"
interface ICreateOrUpdateOptions<K> extends Sequelize.ICreateOptions {
createOrUpdateWith?: Partial<K>
}
export class BaseRepository<T extends Sequelize.Model<T> & K, K> {
protected relation: typeof Sequelize.Model
constructor(relation: typeof Sequelize.Model) {
this.relation = relation
}
public async all(): Promise<K[]> {
return this.relation.all<T>()
}
public async where(attributes: WhereOptions<T>, options: Sequelize.IFindOptions<T> = {}): Promise<K[]> {
return this.relation
.findAll<T>({
...options,
where: attributes,
})
}
public async first(options: Sequelize.IFindOptions<T> = {}): Promise<Nullable<K>> {
return this.relation.findOne<T>({ ...options, order: ["id"] })
}
public async find(id: number): Promise<K> | null {
return this.relation.findById<T>(id)
}
public async count(conditions?: WhereOptions<T>): Promise<number> {
return this.relation.count({
where: conditions ? conditions : {},
})
}
public async create(attributes: any, options: Sequelize.ICreateOptions = {}): Promise<K> {
return this.relation
.create<T>(attributes, options)
}
public async update(id: number, attributes: Partial<K>, options: InstanceUpdateOptions = {}): Promise<K> {
const obj = await this.relation.findById<T>(id)
return obj.update(attributes, { ...options, returning: true })
}
public async updateBy(
searchAttributes: WhereOptions<T>,
attributes: Partial<K>,
options: InstanceUpdateOptions = {},
): Promise<K> {
const object = await this.findBy(searchAttributes)
return this.update((object as any).id, attributes)
}
public async destroy(id: number, options = {}): Promise<void> {
await this.relation
.destroy({
...options,
where: {
id,
},
})
}
public async destroyAll(): Promise<void> {
await this.relation
.destroy({
where: {},
})
}
public async findBy(attributes: WhereOptions<T>, options: Sequelize.IFindOptions<T> = {}): Promise<Nullable<K>> {
return this.relation.findOne<T>({
...options,
where: attributes,
})
}
public async findOrCreateBy(attributes: WhereOptions<T>, options: ICreateOrUpdateOptions<K> = {}): Promise<K> {
return this.findBy(attributes)
.then((object) => {
if (object) {
if (options.createOrUpdateWith) {
return this.update((object as any).id, options.createOrUpdateWith)
} else {
return object
}
}
if (options.createOrUpdateWith) {
Object.assign(attributes, options.createOrUpdateWith)
}
return this.create(attributes, options)
})
}
public async exists(attributes: WhereOptions<T>): Promise<boolean> {
return this.relation
.count({
where: attributes,
}).then((count) => count !== 0)
}
public async updateAll(where: WhereOptions<T>, attributes: Partial<K>, options: any = {}): Promise<number> {
const [count] = await this.relation.update(attributes, { ...options, returning: true, where })
return count
}
public async updateCreatedAt(id: number, newCreatedAt: Date): Promise<K> {
const object = await this.relation.findById<T>(id)
object.set({ createdAt: newCreatedAt })
object.changed("createdAt")
await object.save({ fields: ["createdAt"] })
return object
}
}
I thought about using a derived class of the actual model. This derived class will automatically be created when the model is passed to the sequelize instance (via new Sequelize({modelPaths: [...]}) or sequelize.addModels). So that sequelize will put all its information on the derived class reference and not on the actual one. sequelize.getRepository(User); would return the derived class reference then. The type inference can be achieved like this:
type NonAbstract<T> = {[P in keyof T]: T[P]};
type StaticMembers = NonAbstract<typeof Model>;
type Constructor<T> = (new () => T);
type ModelType<T> = Constructor<T> & StaticMembers;
type Repository<T> = ModelType<T>;
class Sequelize {
...
getRepository<T extends Model<T>>(model: ModelType<T>): Repository<T> {}
}
Of course, the passed model do have the same type as the returned one. But the return value is the derived class reference.
But, what I already suggested with {repositoryMode: true}, this is an optional feature.
@bilby91 At least there is no need for creating another implementation like your BaseRepository anymore :) Can you see any issues with this?
Yep, being able to remove my base class would be awesome. Another important thing IMO is to return plain objects from repositories and not Model instances. By doing this we can improve the separation of concerns and generate clear boundaries.
So you mean
const user = await userRepository.findOne();
user instanceOf User // => false
?
@RobinBuschmann Exactly. Basically, user will be plain object with "form" T
@bilby91 Hmm, I think configuring the ORM via classes and annotations wouldn't be the right way then. So defining a class User which never gets instantiated is not what a user will expect from an ORM like that. I definitely got you're point, but sequelize doesn't seem to be the proper base for that - Sounds like a totally different ORM to me.
Defining models with object literals instead would be a better approach and would not create false expectations.
Since typescript's mapping types this would still give type safety and would save one's from defining duplicate interfaces. Sounds interesting, but shouldn't be part of sequelize-typescript. Do you agree with that?
@RobinBuschmann I sadly (in a good manner 馃槃) have to agree!
Decoupling data-access from model definition sounds really nice. Would make sequelize-typescript more flexible. I think it would be worth the additional complexity, and by introducing it via the repositoryMode option, it wouldn't even necessarily be a breaking change.
Hi, any advance on this? Just asking 馃槤馃き
Sent with GitHawk
@sant123 Currently not, but this will be the next big thing after dropping sequelize v3 support what I will do. (Dropping sequelize v3 support is currently in progress :))
HI,
i forked the repo and implement the repository mode configuration for v4, as suggested below
Check out
https://github.com/alejandrogkl/sequelize-typescript/tree/repository_mode
Now i am able to use the same model with multiple dababases, just need to get the model with sequelize.getRepository(Model)
Hey @alejandrogkl, good job 馃憤
Can you create a pull request, so that we can discuss your code? 馃檪
done
The PR in question is #345, if someone is looking for a link.
Repository mode is available since [email protected]
I'm trying the same approach and I created a very simple repository
export class BaseRepository<T extends Sequelize.Model<T> & K, K> {
protected relation: typeof Sequelize.Model
constructor(relation: typeof Sequelize.Model) {
this.relation = relation
}
public async find(id: number): Promise<K> | null {
return this.relation.findById<T>(id)
}
}
and I'm getting this error: The 'this' context of type 'typeof Model' is not assignable to method's 'this' of type 'new () => T'.
Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2684)
on the this.relation.findById<T>(id)
Is there anything I can do to overcome this issue?
5.0.0-beta.150.6.73.3.3333Hi @SamiSammour
I found the solution, change
protected relation: typeof Sequelize.Model
to
protected relation: {new(): T} & typeof Sequelize.Model
@saefullohmaslul Genius! I was struggling to figure out how to store a typeof T and also be able to call its static methods on SO and this solved it!
Most helpful comment
@sant123 Currently not, but this will be the next big thing after dropping sequelize v3 support what I will do. (Dropping sequelize v3 support is currently in progress :))