Skip to content

Commit

Permalink
feat(core): add EntityRepositoryType symbol (#698)
Browse files Browse the repository at this point in the history
This allows to pass the type of your custom repository in the entity definition,
which will be then used in `em.getRepository(Entity)`.

```typescript
@entity({ customRepository: () => AuthorRepository })
export class Author {

  [EntityRepositoryType]?: AuthorRepository;

}

const repo = em.getRepository(Author); // repo has type AuthorRepository
```

Related: #696
  • Loading branch information
B4nan committed Aug 9, 2020
1 parent 81cd4b0 commit ffae0a8
Show file tree
Hide file tree
Showing 7 changed files with 20 additions and 15 deletions.
6 changes: 3 additions & 3 deletions packages/core/src/EntityManager.ts
Expand Up @@ -5,7 +5,7 @@ import { Configuration, QueryHelper, RequestContext, Utils, ValidationError } fr
import { EntityAssigner, EntityFactory, EntityLoader, EntityRepository, EntityValidator, IdentifiedReference, LoadStrategy, Reference, ReferenceType, wrap } from './entity';
import { LockMode, UnitOfWork } from './unit-of-work';
import { CountOptions, DeleteOptions, EntityManagerType, FindOneOptions, FindOneOrFailOptions, FindOptions, IDatabaseDriver, UpdateOptions } from './drivers';
import { AnyEntity, Dictionary, EntityData, EntityMetadata, EntityName, FilterDef, FilterQuery, Loaded, Primary, Populate, PopulateMap, PopulateOptions, New } from './typings';
import { AnyEntity, Dictionary, EntityData, EntityMetadata, EntityName, FilterDef, FilterQuery, Loaded, Primary, Populate, PopulateMap, PopulateOptions, New, GetRepository } from './typings';
import { QueryOrderMap } from './enums';
import { MetadataStorage } from './metadata';
import { Transaction } from './connections';
Expand Down Expand Up @@ -50,7 +50,7 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
/**
* Gets repository for given entity. You can pass either string name or entity class reference.
*/
getRepository<T extends AnyEntity<T>, U extends EntityRepository<T> = EntityRepository<T>>(entityName: EntityName<T>): U {
getRepository<T extends AnyEntity<T>, U extends EntityRepository<T> = EntityRepository<T>>(entityName: EntityName<T>): GetRepository<T, U> {
entityName = Utils.className(entityName);

if (!this.repositoryMap[entityName]) {
Expand All @@ -59,7 +59,7 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
this.repositoryMap[entityName] = new RepositoryClass(this, entityName);
}

return this.repositoryMap[entityName] as unknown as U;
return this.repositoryMap[entityName] as unknown as GetRepository<T, U>;
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Expand Up @@ -2,6 +2,7 @@
export {
Constructor, Dictionary, PrimaryKeyType, Primary, IPrimaryKey, FilterQuery, IWrappedEntity, EntityName, EntityData,
AnyEntity, EntityProperty, EntityMetadata, QBFilterQuery, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection,
GetRepository, EntityRepositoryType,
} from './typings';
export * from './enums';
export * from './exceptions';
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/typings.ts
Expand Up @@ -25,6 +25,7 @@ export type DeepPartialEntity<T> = {
: DeepPartialEntity<T[P]> | PartialEntity<T[P]> | Primary<T[P]> | OperatorMap<T[P]> | StringProp<T[P]>)
};

export const EntityRepositoryType = Symbol('EntityRepositoryType');
export const PrimaryKeyType = Symbol('PrimaryKeyType');
export type Primary<T> = T extends { [PrimaryKeyType]: infer PK }
? PK : T extends { _id: infer PK }
Expand Down Expand Up @@ -101,12 +102,13 @@ export interface IWrappedEntityInternal<T, PK extends keyof T, P = keyof T> exte
__serializedPrimaryKey: string & keyof T;
}

export type AnyEntity<T = any> = { [K in keyof T]?: T[K] } & { [PrimaryKeyType]?: unknown };
export type AnyEntity<T = any> = { [K in keyof T]?: T[K] } & { [PrimaryKeyType]?: unknown; [EntityRepositoryType]?: unknown };
// eslint-disable-next-line @typescript-eslint/ban-types
export type EntityClass<T extends AnyEntity<T>> = Function & { prototype: T };
export type EntityClassGroup<T extends AnyEntity<T>> = { entity: EntityClass<T>; schema: EntityMetadata<T> | EntitySchema<T> };
export type EntityName<T extends AnyEntity<T>> = string | EntityClass<T> | EntitySchema<T, any>;
export type EntityData<T extends AnyEntity<T>, P extends Populate<T> = keyof T> = { [K in keyof T]?: T[K] | Primary<T[K]> | EntityData<T[K]> | CollectionItem<T[K]>[] } & Dictionary;
export type GetRepository<T extends AnyEntity<T>, U> = T[typeof EntityRepositoryType] extends EntityRepository<any> | undefined ? NonNullable<T[typeof EntityRepositoryType]> : U;

export interface EntityProperty<T extends AnyEntity<T> = any> {
name: string & keyof T;
Expand Down
4 changes: 2 additions & 2 deletions packages/knex/src/SqlEntityManager.ts
@@ -1,5 +1,5 @@
import Knex, { QueryBuilder as KnexQueryBuilder, Raw } from 'knex';
import { AnyEntity, EntityData, EntityManager, EntityName, EntityRepository, QueryResult, Utils } from '@mikro-orm/core';
import { AnyEntity, EntityData, EntityManager, EntityName, EntityRepository, GetRepository, QueryResult, Utils } from '@mikro-orm/core';
import { AbstractSqlDriver } from './AbstractSqlDriver';
import { QueryBuilder } from './query';
import { SqlEntityRepository } from './SqlEntityRepository';
Expand All @@ -25,7 +25,7 @@ export class SqlEntityManager<D extends AbstractSqlDriver = AbstractSqlDriver> e
return this.getDriver().execute(queryOrKnex, params, method, this.getTransactionContext());
}

getRepository<T extends AnyEntity<T>, U extends EntityRepository<T> = SqlEntityRepository<T>>(entityName: EntityName<T>): U {
getRepository<T extends AnyEntity<T>, U extends EntityRepository<T> = SqlEntityRepository<T>>(entityName: EntityName<T>): GetRepository<T, U> {
return super.getRepository<T, U>(entityName);
}

Expand Down
4 changes: 2 additions & 2 deletions packages/mongodb/src/MongoEntityManager.ts
@@ -1,4 +1,4 @@
import { AnyEntity, EntityManager, EntityName, EntityRepository, Utils } from '@mikro-orm/core';
import { AnyEntity, EntityManager, EntityName, EntityRepository, GetRepository, Utils } from '@mikro-orm/core';
import { MongoDriver } from './MongoDriver';
import { MongoEntityRepository } from './MongoEntityRepository';
import { Collection } from 'mongodb';
Expand All @@ -20,7 +20,7 @@ export class MongoEntityManager<D extends MongoDriver = MongoDriver> extends Ent
return this.getConnection().getCollection(entityName);
}

getRepository<T extends AnyEntity<T>, U extends EntityRepository<T> = MongoEntityRepository<T>>(entityName: EntityName<T>): U {
getRepository<T extends AnyEntity<T>, U extends EntityRepository<T> = MongoEntityRepository<T>>(entityName: EntityName<T>): GetRepository<T, U> {
return super.getRepository<T, U>(entityName);
}

Expand Down
12 changes: 6 additions & 6 deletions tests/EntityManager.mongo.test.ts
Expand Up @@ -160,7 +160,7 @@ describe('EntityManagerMongo', () => {
});

test('should provide custom repository', async () => {
const repo = orm.em.getRepository(Author) as AuthorRepository;
const repo = orm.em.getRepository(Author);
expect(repo).toBeInstanceOf(AuthorRepository);
expect(repo.magic).toBeInstanceOf(Function);
expect(repo.magic('test')).toBe('111 test 222');
Expand Down Expand Up @@ -256,7 +256,7 @@ describe('EntityManagerMongo', () => {
});

test('findOne should work with options parameter', async () => {
const repo = orm.em.getRepository(Author) as AuthorRepository;
const repo = orm.em.getRepository(Author);
const author = new Author('name 1', 'email1');
const author2 = new Author('name 2', 'email2');
await repo.persistAndFlush([author, author2]);
Expand All @@ -282,7 +282,7 @@ describe('EntityManagerMongo', () => {
});

test('should convert entity to PK when trying to search by entity', async () => {
const repo = orm.em.getRepository(Author) as AuthorRepository;
const repo = orm.em.getRepository(Author);
const author = new Author('name', 'email');
author.favouriteAuthor = author;
await repo.persistAndFlush(author);
Expand All @@ -296,7 +296,7 @@ describe('EntityManagerMongo', () => {
const author = new Author('name', 'email');
const author2 = new Author('name2', 'email2');
const author3 = new Author('name3', 'email3');
const repo = orm.em.getRepository(Author) as AuthorRepository;
const repo = orm.em.getRepository(Author);
repo.persist(author);
repo.persist(author2);
await repo.removeAndFlush(author);
Expand All @@ -309,7 +309,7 @@ describe('EntityManagerMongo', () => {

test('removing persisted entity will remove it from persist stack first', async () => {
const author = new Author('name', 'email');
const repo = orm.em.getRepository(Author) as AuthorRepository;
const repo = orm.em.getRepository(Author);
await repo.persistAndFlush(author);
expect(orm.em.getUnitOfWork().getById<Author>(Author.name, author.id)).toBeDefined();
author.name = 'new name';
Expand All @@ -321,7 +321,7 @@ describe('EntityManagerMongo', () => {

test('removing persisted entity via PK', async () => {
const author = new Author('name', 'email');
const repo = orm.em.getRepository(Author) as AuthorRepository;
const repo = orm.em.getRepository(Author);
await repo.persistAndFlush(author);
orm.em.clear();

Expand Down
4 changes: 3 additions & 1 deletion tests/entities/Author.ts
@@ -1,6 +1,6 @@
import {
AfterCreate, AfterDelete, AfterUpdate, BeforeCreate, BeforeDelete, BeforeUpdate, DateType, Collection,
Cascade, Entity, ManyToMany, ManyToOne, OneToMany, Property, Index, Unique, EntityAssigner,
Cascade, Entity, ManyToMany, ManyToOne, OneToMany, Property, Index, Unique, EntityAssigner, EntityRepositoryType,
} from '@mikro-orm/core';

import { Book } from './Book';
Expand All @@ -11,6 +11,8 @@ import { BaseEntity } from './BaseEntity';
@Index({ name: 'custom_idx_1', properties: ['name', 'email'] })
export class Author extends BaseEntity<Author> {

[EntityRepositoryType]: AuthorRepository;

static beforeDestroyCalled = 0;
static afterDestroyCalled = 0;

Expand Down

0 comments on commit ffae0a8

Please sign in to comment.