Skip to content

Commit

Permalink
feat: refactor extendable service to be repository based
Browse files Browse the repository at this point in the history
  • Loading branch information
etienne-bechara committed Aug 4, 2021
1 parent b2ad415 commit 2748a2a
Show file tree
Hide file tree
Showing 38 changed files with 977 additions and 887 deletions.
111 changes: 66 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ npm i @bechara/nestjs-orm

2\. Add these example variables to your `.env` (adjust accordingly):

```
```bash
# Standard connection
ORM_TYPE='mysql'
ORM_HOST='localhost'
ORM_PORT=3306
ORM_USERNAME='root'
ORM_PASSWORD='1234'
ORM_PASSWORD=''
ORM_DATABASE='test'

# SSL options
ORM_SERVER_CA=''
ORM_CLIENT_CERTIFICATE=''
ORM_CLIENT_KEY=''
```

It is recommended that you have a local database in order to test connectivity.
Expand Down Expand Up @@ -92,38 +98,36 @@ We may simplify the process of adding data storage functionality as:

Please refer to the official [Defining Entities](https://mikro-orm.io/docs/defining-entities) documentation from MikroORM.

### Creating an Entity Service
### Creating an Entity Repository

In order to create a new entity service, extend the provided abstract service from this package and inject an repository instance.
In order to create a new entity repository, extend the provided abstract repository from this package.

Then, you should call its super method passing this instance as well as an optional object with further customizations.

Example:

```ts
import { OrmService, EntityRepository, InjectRepository } from '@bechara/nestjs-orm';
import { Injectable } from '@bechara/nestjs-core';
import { EntityManager, EntityName, OrmRepository, Repository } from '@bechara/nestjs-orm';

// This entity is whatever you defined in the previous step
import { UserEntity } from './user.entity';
import { User } from './user.entity';

@Injectable()
export class UserService extends OrmService<UserEntity> {
@Repository(User)
export class UserRepository extends OrmRepository<User> {

public constructor(
@InjectRepository(UserEntity)
private readonly userRepository: EntityRepository<UserEntity>,
protected readonly entityManager: EntityManager,
protected readonly entityName: EntityName<User>,
) {
super(userRepository, {
defaultUniqueKey: [ 'name' ], // [Optional] Combination of fields to enable entity upsert
defaultPopulate: [ 'employers' ], // [Optional] Nested entities to automatic load when no 'populate' option is provided
super(entityManager, entityName, {
entityName: 'user',
defaultUniqueKey: [ 'name', 'surname' ],
});
}

}
```

At this point, you may inject you `UserService` in any order provider and have its methods available to you:
At this point, an injectable `UserRepository` will be available throughout the application, exposing extra ORM functionalities:

```ts
// Read entities
Expand All @@ -134,13 +138,14 @@ readByIdOrFail(): Promise<Entity>;
readUnique(): Promise<Entity>;
readUniqueOrFail(): Promise<Entity>;
readAndCount(): Promise<OrmPaginatedResponse<Entity>>;
load(): Promise<void>
reload(): Promise<Entity[]>;

// Create entities
create(): Promise<Entity[]>;
createOne(): Promise<Entity>;
readOrCreate(): Promise<Entity[]>;
readOrCreateOne(): Promise<Entity>;
insert(): Promise<Entity[]>;
insertOne(): Promise<Entity>;
readOrInsert(): Promise<Entity[]>;
readOrInsertOne(): Promise<Entity>;

// Update entities
update(): Promise<Entity[]>;
Expand All @@ -150,30 +155,48 @@ upsert(): Promise<Entity[]>;
upsertOne(): Promise<Entity>;

// Remove entities
remove(): Promise<Entity[]>;
removeOne(): Promise<Entity>;
removeById(id: string): Promise<Entity>;
delete(): Promise<Entity[]>;
deleteOne(): Promise<Entity>;
deleteById(id: string): Promise<Entity>;
```
You may also extending the functionality of these built-in methods with these provided hooks:
### Creating an Subscriber
If you would like to create hooks when triggering certain operations, it is possible by defining an injectable subscriber:
```ts
beforeRead(): Promise<OrmReadParams<Entity>>;
afterRead(): Promise<Entity>;
import { Injectable, LoggerService } from '@bechara/nestjs-core';
import { EntityManager, OrmSubscriber, OrmSubscriberParams } from '@bechara/nestjs-orm';

import { User } from './user.entity';

@Injectable()
export class UserSubscriber implements OrmSubscriber<User> {

beforeCreate(): Promise<EntityData<Entity>>;
afterCreate(): Promise<Entity>;
public constructor(
private readonly entityManager: EntityManager,
private readonly loggerService: LoggerService,
) {
entityManager.getEventManager().registerSubscriber(this);
}

beforeUpdate(): Promise<OrmUpdateParams<Entity>>;
afterUpdate(): Promise<Entity>;
/**
* Before update hook example.
* @param params
*/
public beforeUpdate(params: OrmSubscriberParams<User>): Promise<void> {
const { changeSet } = params;
this.loggerService.warning('beforeUpdate: changeSet', changeSet);
return;
}

beforeRemove(): Promise<Entity>;
afterRemove(): Promise<Entity>;
}
```


### Creating an Entity Controller

Finally, expose a controller injecting your service as dependency to allow manipulation through HTTP requests.
Finally, expose a controller injecting your repository as dependency to allow manipulation through HTTP requests.

If you are looking for CRUD functionality you may copy exactly as the template below.

Expand All @@ -189,50 +212,48 @@ import { UserEntity } from './user.entity';
import { UserService } from './user.service';

@Controller('user')
export class UserController extends OrmController<UserEntity> {
export class UserController {

public constructor(
private readonly userService: UserService,
) {
super(userService);
}
private readonly userRepository: UserRepository,
) { }

@Get()
public async get(@Query() query: UserReadDto): Promise<OrmPagination<UserEntity>> {
// getReadArguments() is a built-in method that extracts pagination
// properties like sort, order, limit and offset
const { params, options } = this.getReadArguments(query);
return this.entityService.readAndCount(params, options);
return this.userRepository.readAndCount(params, options);
}

@Get(':id')
public async getById(@Param('id') id: string): Promise<UserEntity> {
return this.entityService.readByIdOrFail(id);
return this.userRepository.readByIdOrFail(id);
}

@Post()
public async post(@Body() body: UserCreateDto): Promise<UserEntity> {
return this.entityService.createOne(body);
return this.userRepository.insertOne(body);
}

@Put()
public async put(@Body() body: UserCreateDto): Promise<UserEntity> {
return this.entityService.upsertOne(body);
return this.userRepository.upsertOne(body);
}

@Put(':id')
public async putById(@Param('id') id: string, @Body() body: UserCreateDto): Promise<UserEntity> {
return this.entityService.updateById(id, body);
return this.userRepository.updateById(id, body);
}

@Patch(':id')
public async patchById(@Param('id') id: string, @Body() body: UserUpdateDto): Promise<UserEntity> {
return this.entityService.updateById(id, body);
return this.userRepository.updateById(id, body);
}

@Delete(':id')
public async deleteById(@Param('id') id: string): Promise<UserEntity> {
return this.entityService.removeById(id);
return this.userRepository.deleteById(id);
}

}
Expand Down
1 change: 0 additions & 1 deletion source/orm/orm.enum/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './orm.controller.method';
export * from './orm.injection.token';
export * from './orm.query.order';
8 changes: 0 additions & 8 deletions source/orm/orm.enum/orm.controller.method.ts

This file was deleted.

6 changes: 3 additions & 3 deletions source/orm/orm.interface/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export * from './orm.create.options';
export * from './orm.delete.options';
export * from './orm.module.options';
export * from './orm.pagination';
export * from './orm.read.arguments';
export * from './orm.read.options';
export * from './orm.read.params';
export * from './orm.request.validation';
export * from './orm.service.options';
export * from './orm.repository.options';
export * from './orm.subscriber';
export * from './orm.update.options';
export * from './orm.update.params';
export * from './orm.upsert.options';
export * from './orm.validation.data';
1 change: 1 addition & 0 deletions source/orm/orm.interface/orm.create.options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { OrmReadOptions } from './orm.read.options';

export interface OrmCreateOptions<Entity> extends OrmReadOptions<Entity> {
disableFlush?: boolean;
disableReload?: boolean;
}
3 changes: 3 additions & 0 deletions source/orm/orm.interface/orm.delete.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface OrmDeleteOptions {
disableFlush?: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Populate } from '@mikro-orm/core';

export interface OrmServiceOptions<Entity> {
export interface OrmRepositoryOptions<Entity> {
entityName?: string;
primaryKey?: string;
nestedPrimaryKeys?: string[];
Expand Down
8 changes: 0 additions & 8 deletions source/orm/orm.interface/orm.request.validation.ts

This file was deleted.

5 changes: 5 additions & 0 deletions source/orm/orm.interface/orm.subscriber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { EventArgs, EventSubscriber } from '@mikro-orm/core';

export type OrmSubscriber<Entity> = EventSubscriber<Entity>;

export type OrmSubscriberParams<Entity> = EventArgs<Entity>;
4 changes: 0 additions & 4 deletions source/orm/orm.interface/orm.validation.data.ts

This file was deleted.

17 changes: 17 additions & 0 deletions source/orm/orm.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { EntityManager, EntityName } from '@mikro-orm/core';

import { OrmRepositoryOptions } from './orm.interface';
import { OrmDeleteRepository } from './orm.repository/orm.repository.delete';

export abstract class OrmRepository<T> extends OrmDeleteRepository<T> {

public constructor(
protected readonly entityManager: EntityManager,
protected readonly entityName: EntityName<T>,
protected readonly repositoryOptions: OrmRepositoryOptions<T> = { },
) {
super(entityManager, entityName, repositoryOptions);
this.repositoryOptions.entityName ??= 'entity';
}

}
5 changes: 5 additions & 0 deletions source/orm/orm.repository/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './orm.repository.base';
export * from './orm.repository.create';
export * from './orm.repository.delete';
export * from './orm.repository.read';
export * from './orm.repository.update';
Loading

0 comments on commit 2748a2a

Please sign in to comment.