npm package: @rafikidota/iroh
Description: Sometimes, the best way to solve your own problems is to help someone else.
Author: rafiki
Latest version: 0.53.0

# Iroh

Sometimes, the best way to solve your own problems is to help someone else.

## Using Iroh with Nestjs

The following TypeScript code snippet illustrates an example of using the Iroh library within the Nestjs framework. This code establishes a basic Nestjs CRUD and highlights how to implement Iroh in your Nestjs project.

## Prerequisites
Before using the schematics, ensure you have the following:
- A NestJS project set up
- TypeORM configured
- Necessary dependencies installed

## Step-by-Step Guide

## 1. Generate Basic Modules
Run the following command to generate the `core` and `common` modules:
```sh
npx nest g -c @rafikidota/iroh init 
```

## 2. Import Basic Modules on AppModule

```ts
import { Module } from '@nestjs/common';
import { NestModule } from '@nestjs/common';
import { MiddlewareConsumer } from '@nestjs/common';
import { StartTimeMiddleware } from '@rafikidota/iroh';
import { CommonModule } from './common/common.module';
import { CoreModule } from './modules/core.module';

@Module({
  imports: [CommonModule, CoreModule],
  controllers: [],
  providers: [],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(StartTimeMiddleware).forRoutes('/*path');
  }
}
```

## 3. Generate CRUD Module
Run the following command to generate a new `CRUD` module:
```sh
npx nest g -c @rafikidota/iroh crud <module-name>
```

## 4. Output files

### Module
Import necessary modules and set up your feature module:
```ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { HeroController } from './hero.controller';
import { HeroService } from './hero.service';
import { HeroRepository } from './infra/hero.repository';
import { HeroPersistence } from './infra/hero.persistence';

@Module({
  controllers: [HeroController],
  providers: [HeroService, HeroRepository],
  imports: [TypeOrmModule.forFeature([HeroPersistence])],
  exports: [TypeOrmModule],
})
export class HeroModule {}
```

### Controller
Define your controller by extending `GenericController`:
```ts
import { Controller } from '@nestjs/common';
import { Query } from '@nestjs/common';
import { Body } from '@nestjs/common';
import { Param } from '@nestjs/common';
import { ParseUUIDPipe } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';

import { IrohController } from '@rafikidota/iroh';
import { GenericPost } from '@rafikidota/iroh';
import { GenericGet } from '@rafikidota/iroh';
import { GenericGetById } from '@rafikidota/iroh';
import { GenericPatch } from '@rafikidota/iroh';
import { GenericDelete } from '@rafikidota/iroh';
import { ParseQueryPipe } from '@rafikidota/iroh';
import { SearchDto } from '@rafikidota/iroh';
import { QueryOptionDto } from '@rafikidota/iroh';

import { HeroService } from './hero.service';
import { HeroMapper } from './infra/hero.mapper';
import { CreateHeroDto } from './app/dto/hero.create.dto';
import { UpdateHeroDto } from './app/dto/hero.update.dto';
import { HeroView } from './app/dto/hero.view';

@ApiTags('Hero')
@Controller('hero')
export class HeroController extends IrohController<HeroMapper> {
  constructor(readonly service: HeroService) {
    super(service);
  }

  @GenericPost(CreateHeroDto, HeroView)
  create(body: CreateHeroDto): Promise<HeroView> {
    return super.create(body);
  }

  @GenericGet(HeroView)
  paginate(@Query(ParseQueryPipe) query: SearchDto): Promise<HeroView[]> {
    return super.paginate(query);
  }

  @GenericGetById(HeroView)
  findById(
    @Param('id', ParseUUIDPipe) id: string,
    @Query(ParseQueryPipe) query: QueryOptionDto,
  ): Promise<HeroView> {
    return super.findById(id, query);
  }

  @GenericPatch(UpdateHeroDto, HeroView)
  update(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() body: UpdateHeroDto,
  ): Promise<HeroView> {
    return super.update(id, body);
  }

  @GenericDelete()
  remove(@Param('id', ParseUUIDPipe) id: string): Promise<HeroView> {
    return super.remove(id);
  }
}
```

### Service
Define your service by extending `GenericService`:
```ts
import { Injectable } from '@nestjs/common';
import { IrohService } from '@rafikidota/iroh';
import { HeroRepository } from './infra/hero.repository';
import { HeroMapper } from './infra/hero.mapper';

@Injectable()
export class HeroService extends IrohService<HeroMapper> {
  constructor(readonly repository: HeroRepository) {
    super(repository);
  }
}
```

### Repository
Define your repository by extending `GenericTypeOrmRepository`:
```ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { IrohRepository } from '@rafikidota/iroh';
import { HeroMapper } from './hero.mapper';
import { HeroPersistence } from './hero.persistence';

@Injectable()
export class HeroRepository extends IrohRepository<HeroMapper> {
  constructor(
    @InjectRepository(HeroPersistence)
    repository: Repository<HeroPersistence>,
  ) {
    super(repository);
    this.mapper = new HeroMapper();
  }
}
```

### Persistence
Define your persistence by extending `Persistence`:
```ts
import { Column, Entity } from 'typeorm';
import { Persistence } from '@rafikidota/iroh';
import { IHero } from '../domain';

@Entity('hero')
export class HeroPersistence extends Persistence implements IHero {
  @Column()
  name: string;
}
```

### Domain
Define your domain by extending `Domain`:
```ts
import { DeepPartial } from 'typeorm';
import { Result } from '@rafikidota/iroh';
import { DomainFactory } from '@rafikidota/iroh';
import { Domain } from '@rafikidota/iroh';
import { IUpdatableDomain } from '@rafikidota/iroh';
import { Props } from '@rafikidota/iroh';
import { IHero } from './hero.interface';

export type HeroProps = Props<HeroDomain>;

export type NewHeroProps = Omit<HeroProps, ''>;

export type UpdateHeroProps = DeepPartial<NewHeroProps>;

export type IHeroDomain = IHero & IUpdatableDomain<HeroDomain>;

export class HeroDomain extends Domain implements IHeroDomain {
  name: string;

  static create(props: NewHeroProps): Result<HeroDomain> {
    const domain = DomainFactory.create(HeroDomain, { ...props });
    return Result.Ok(domain);
  }

  public update(props: UpdateHeroProps): Result<HeroDomain> {
    Object.assign(this, { updatedAt: new Date(), ...props });
    return Result.Ok(this);
  }
}
```

### View
Define your view by extending `OmitType` from the `IntersectionType` of `PartialType(CreateHeroDto)` and `View`:
```ts
import { ApiProperty } from '@nestjs/swagger';
import { IntersectionType } from '@nestjs/swagger';
import { OmitType } from '@nestjs/swagger';
import { PartialType } from '@nestjs/swagger';
import { View } from '@rafikidota/iroh';
import { CreateHeroDto } from './hero.create.dto';
import { HeroDomain, IHero } from '../../domain';

export class HeroView
  extends OmitType(
    IntersectionType(PartialType(CreateHeroDto), View),
    [],
  )
  implements IHero
{
  @ApiProperty()
  id: string;
  @ApiProperty({
    example: 'Pudge',
  })
  name: string;
  @ApiProperty()
  createdAt: Date;
  @ApiProperty()
  updatedAt: Date;

  constructor(domain: HeroDomain) {
    super();
    this.id = domain.id;
    this.name = domain.name;
    this.createdAt = domain.createdAt;
    this.updatedAt = domain.updatedAt;
  }
}
```

### Mapper
Define your mapper by extending `GenericEntityMapper`:
```ts
import { DomainFactory } from '@rafikidota/iroh';
import { PersistenceFactory } from '@rafikidota/iroh';
import { DomainProps } from '@rafikidota/iroh';
import { IrohMapper } from '@rafikidota/iroh';
import { HeroPersistence } from './hero.persistence';
import { HeroDomain } from '../domain/hero.domain';
import { HeroView } from '../app/dto/hero.view';

const Mapper = IrohMapper<HeroPersistence, HeroDomain, HeroView>;

export class HeroMapper extends Mapper {
  PersistenceToDomain(persistence: HeroPersistence): HeroDomain {
    const props: Partial<DomainProps<HeroDomain>> = {
      id: persistence.id,
      name: persistence.name,
      createdAt: persistence.createdAt,
      updatedAt: persistence.updatedAt,
      deletedAt: persistence.deletedAt,
    };
    return DomainFactory.instance(HeroDomain, { ...props });
  }

  DomainToPersistence(domain: HeroDomain): HeroPersistence {
    const props: Partial<DomainProps<HeroDomain>> = {
      id: domain.id,
      name: domain.name,
      createdAt: domain.createdAt,
      updatedAt: domain.updatedAt,
      deletedAt: domain.deletedAt,
    };
    return PersistenceFactory.create(HeroPersistence, { ...props });
  }

  DomainToView(domain: HeroDomain): HeroView {
    return new HeroView(domain);
  }
}
```

## Additional Resources
- [NestJS Documentation](https://docs.nestjs.com/)
- [TypeORM Documentation](https://typeorm.io/)
- [Nestjs Iroh example on GitHub](https://github.com/rafikidota/nestjs-iroh/)