Skip to content
This repository was archived by the owner on Jan 7, 2021. It is now read-only.

Commit 1c889ae

Browse files
author
Chau Tran
committed
feat(automapper.module.ts): add profile decorator
Profile decorator deprecates forFeature BREAKING CHANGE: forFeature has been removed in favor of Profile decorator #2
1 parent 064cb4a commit 1c889ae

10 files changed

Lines changed: 109 additions & 144 deletions

README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,15 @@ Both options are optional. If you pass in `config` and configure your `AutoMappe
6969

7070
2. `AutoMapper` has a concept of `Profile`. A `Profile` is a class that will house some specific mappings related to a specific domain model. Eg: `User` mappings will be housed by `UserProfile`. Refer to [@nartc/automapper: usage](https://github.com/nartc/mapper#usage) for more information regarding `Profile`.
7171

72-
`NestJS` recommends you to separate features/domains in your application into `Modules`, in each module you would import/declare other modules/parts that are related to that Module. `AutomapperModule` also has a static method `forFeature` which should be used in such a feature module. `forFeature` accepts an `AutomapperModuleFeatureOptions` which has:
73-
- `profiles`: An array of `Profiles` related to this module, and this will be added to an `AutoMapper` instance.
74-
- `name`: Decide which `AutoMapper` instance to add these profiles to. Default to `"default"`
72+
`nestjsx-automapper` exposes a `@Profile()` to decorate your `Profile` class.
7573

7674
```typescript
77-
@Module({
78-
imports: [AutomapperModule.forFeature({profiles: [new UserProfile()]})]
79-
})
80-
export class UserModule {}
75+
@Profile()
76+
class UserProfile extends MappingProfileBase {}
8177
```
8278

79+
`@Profile(name?: string)` takes in an optional `name` parameter, this is the `name` of the `AutoMapper` instance you want to add this Profile on to.
80+
8381
3. Inject an instance of `AutoMapper` in your `Service`:
8482

8583
```typescript

src/automapper.explorer.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { AutoMapper, MappingProfile } from '@nartc/automapper';
2+
import { Inject, Injectable, Logger } from '@nestjs/common';
3+
import { Reflector } from '@nestjs/core';
4+
import { MAPPER_MAP } from './utils/mapperMap';
5+
import { PROFILE_MAP } from './utils/profileMap';
6+
7+
@Injectable()
8+
export class AutomapperExplorer {
9+
constructor(
10+
private readonly reflector: Reflector,
11+
private readonly logger: Logger,
12+
@Inject(MAPPER_MAP) private readonly mapperMap: Map<string, AutoMapper>,
13+
@Inject(PROFILE_MAP)
14+
private readonly profileMap: Map<
15+
string,
16+
new (...args: any) => MappingProfile
17+
>
18+
) {}
19+
20+
explore(): void {
21+
if (!this.profileMap.size) {
22+
return;
23+
}
24+
25+
this.profileMap.forEach(this.exploreProfile.bind(this));
26+
}
27+
28+
private exploreProfile(value: new (...args: any) => MappingProfile) {
29+
const mapperKey = this.reflector.get<string>('AUTO_MAPPER_PROFILE', value);
30+
const mapper = this.mapperMap.get(mapperKey);
31+
32+
if (!mapper) {
33+
this.logger.error(
34+
`There is no Mapper associated with name ${mapperKey.split('__').pop()}`
35+
);
36+
return;
37+
}
38+
39+
mapper.addProfile(new value());
40+
}
41+
}

src/automapper.module.ts

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { AutoMapper } from '@nartc/automapper';
2-
import { DynamicModule, Global, Logger, Module } from '@nestjs/common';
3-
import { forFeatureProviders, forRootProviders } from './automapper.provider';
4-
import {
5-
AutomapperModuleFeatureOptions,
6-
AutomapperModuleRootOptions,
7-
} from './interfaces';
2+
import { DynamicModule, Logger, Module } from '@nestjs/common';
3+
import { OnModuleInit } from '@nestjs/common/interfaces';
4+
import { AutomapperExplorer } from './automapper.explorer';
5+
import { forRootProviders } from './automapper.provider';
6+
import { AutomapperModuleRootOptions } from './interfaces';
7+
import { MAPPER_MAP, MapperMap } from './utils/mapperMap';
8+
import { PROFILE_MAP, ProfileMap } from './utils/profileMap';
89

9-
@Global()
1010
@Module({})
11-
export class AutomapperModule {
11+
export class AutomapperModule implements OnModuleInit {
1212
private static readonly logger: Logger = new Logger('AutomapperModule');
1313

1414
/**
@@ -26,29 +26,20 @@ export class AutomapperModule {
2626

2727
return {
2828
module: AutomapperModule,
29-
providers,
29+
providers: [
30+
...providers,
31+
AutomapperExplorer,
32+
{ provide: PROFILE_MAP, useValue: ProfileMap },
33+
{ provide: MAPPER_MAP, useValue: MapperMap },
34+
{ provide: Logger, useValue: this.logger },
35+
],
3036
exports: providers,
3137
};
3238
}
3339

34-
/**
35-
* Add to the AutoMapper instance a list of MappingProfiles. By default, the instance with name "default" will be
36-
* used.
37-
*
38-
* @param {AutomapperModuleFeatureOptions} options
39-
*/
40-
static forFeature(options: AutomapperModuleFeatureOptions): DynamicModule {
41-
if (!options || (options && !options.profiles.length)) {
42-
const message = 'AutomapperModuleFeatureOptions.profiles is empty';
43-
this.logger.error(message);
44-
throw new Error(message);
45-
}
46-
const providers = forFeatureProviders(options);
47-
return {
48-
module: AutomapperModule,
49-
imports: [AutomapperModule],
50-
providers,
51-
exports: providers,
52-
};
40+
constructor(private readonly explorer: AutomapperExplorer) {}
41+
42+
onModuleInit(): void {
43+
this.explorer.explore();
5344
}
5445
}

src/automapper.provider.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { AutoMapper } from '@nartc/automapper';
22
import { Provider } from '@nestjs/common';
3-
import {
4-
AutomapperModuleFeatureOptions,
5-
AutomapperModuleRootOptions,
6-
} from './interfaces';
3+
import { AutomapperModuleRootOptions } from './interfaces';
74
import { getMapperToken } from './utils/getMapperToken';
8-
import { MAPPER_MAP, MapperMap } from './utils/mapperMap';
5+
import { MapperMap } from './utils/mapperMap';
96

107
export const forRootProviders = (
118
mapper: AutoMapper,
@@ -19,28 +16,5 @@ export const forRootProviders = (
1916
provide: token,
2017
useValue: mapper,
2118
},
22-
{
23-
provide: MAPPER_MAP,
24-
useValue: MapperMap,
25-
},
26-
];
27-
};
28-
29-
export const forFeatureProviders = (
30-
options: AutomapperModuleFeatureOptions
31-
): Provider[] => {
32-
const token = getMapperToken(options ? options.name : '');
33-
const _mapper = MapperMap.has(token)
34-
? (MapperMap.get(token) as AutoMapper)
35-
: new AutoMapper();
36-
37-
options.profiles.forEach(_mapper.addProfile.bind(_mapper));
38-
!MapperMap.has(token) && MapperMap.set(token, _mapper);
39-
40-
return [
41-
{
42-
provide: token,
43-
useValue: _mapper,
44-
},
4519
];
4620
};

src/decorators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './inject-mapper.decorator';
2+
export * from './profile.decorator';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { SetMetadata } from '@nestjs/common/decorators';
2+
import { getMapperToken } from '../utils/getMapperToken';
3+
import { ProfileMap } from '../utils/profileMap';
4+
5+
export const Profile: (name?: string) => ClassDecorator = (name?: string) => (
6+
target: any
7+
) => {
8+
!ProfileMap.has(target) && ProfileMap.set(target, target);
9+
SetMetadata('AUTO_MAPPER_PROFILE', getMapperToken(name))(target);
10+
};
Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AutoMapperConfiguration, MappingProfile } from '@nartc/automapper';
1+
import { AutoMapperConfiguration } from '@nartc/automapper';
22

33
export interface AutomapperModuleRootOptions {
44
/**
@@ -15,17 +15,3 @@ export interface AutomapperModuleRootOptions {
1515
*/
1616
name?: string;
1717
}
18-
19-
export interface AutomapperModuleFeatureOptions {
20-
/**
21-
* An array of MappingProfile to be added to the AutoMapper instance
22-
*/
23-
profiles: Array<MappingProfile>;
24-
25-
/**
26-
* Name of the AutoMapper instance
27-
*
28-
* @default default
29-
*/
30-
name?: string;
31-
}

src/utils/profileMap.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { MappingProfile } from '@nartc/automapper';
2+
3+
export const PROFILE_MAP = 'nestjs__PROFILE_MAP';
4+
export const ProfileMap: Map<
5+
string,
6+
new (...args: any) => MappingProfile
7+
> = new Map<string, { new (...args: any): MappingProfile }>();

test/automapper.test.ts

Lines changed: 21 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AutoMapper, MappingProfileBase } from '@nartc/automapper';
22
import { Module } from '@nestjs/common';
33
import { Test, TestingModule } from '@nestjs/testing';
44
import { Expose } from 'class-transformer';
5-
import { AutomapperModule } from '../src';
5+
import { AutomapperModule, Profile } from '../src';
66
import { getMapperToken } from '../src/utils/getMapperToken';
77
import { MAPPER_MAP } from '../src/utils/mapperMap';
88

@@ -16,6 +16,7 @@ class MockVm {
1616
bar!: string;
1717
}
1818

19+
@Profile()
1920
class MockProfile extends MappingProfileBase {
2021
constructor() {
2122
super();
@@ -29,82 +30,37 @@ class MockProfile extends MappingProfileBase {
2930
}
3031
}
3132

32-
@Module({
33-
imports: [AutomapperModule.forRoot()],
34-
})
35-
class MockModule {}
33+
class Another {
34+
@Expose()
35+
baz!: string;
36+
}
3637

37-
@Module({
38-
imports: [
39-
AutomapperModule.forRoot({
40-
config: cfg => {
41-
cfg.addProfile(new MockProfile());
42-
},
43-
}),
44-
],
45-
})
46-
class MockModuleWithConfig {}
38+
@Profile()
39+
class AnotherProfile extends MappingProfileBase {
40+
constructor() {
41+
super();
42+
}
43+
44+
configure(mapper: AutoMapper): void {
45+
mapper.createMap(Another, MockVm);
46+
}
47+
}
4748

4849
@Module({
49-
imports: [AutomapperModule.forFeature({ profiles: [new MockProfile()] })],
50+
imports: [AutomapperModule.forRoot()],
5051
})
51-
class MockSubModule {}
52-
53-
describe('AutomapperModule - with config', () => {
54-
let moduleFixture: TestingModule;
55-
let mapper: AutoMapper;
56-
let mapperMap: Map<string, AutoMapper>;
57-
58-
beforeAll(async () => {
59-
moduleFixture = await Test.createTestingModule({
60-
imports: [MockModuleWithConfig],
61-
}).compile();
62-
mapperMap = moduleFixture.get<Map<string, AutoMapper>>(MAPPER_MAP);
63-
mapper = moduleFixture.get<AutoMapper>(getMapperToken());
64-
});
65-
66-
afterAll(() => {
67-
mapper.dispose();
68-
});
69-
70-
it('AutomapperModule has been initialized with config', () => {
71-
expect(mapperMap.size).toBeGreaterThan(0);
72-
expect(mapper).toBeTruthy();
73-
expect(JSON.stringify(mapper)).toEqual(
74-
JSON.stringify(mapperMap.get(getMapperToken()))
75-
);
76-
});
77-
78-
it('AutomapperModule - map with config', () => {
79-
const _mock = new Mock();
80-
_mock.foo = 'baz';
81-
82-
const vm = mapper.map(_mock, MockVm);
83-
expect(vm).toBeTruthy();
84-
expect(vm.bar).toEqual(_mock.foo);
85-
expect(vm).toBeInstanceOf(MockVm);
86-
});
87-
88-
it('AutomapperModule - reverseMap with config', () => {
89-
const _mockVm = new MockVm();
90-
_mockVm.bar = 'should be foo';
91-
92-
const _mock = mapper.map(_mockVm, Mock);
93-
expect(_mock).toBeTruthy();
94-
expect(_mock).toBeInstanceOf(Mock);
95-
expect(_mock.foo).toEqual(_mockVm.bar);
96-
});
97-
});
52+
class MockModule {}
9853

99-
describe('AutoMapperModuke', () => {
54+
describe('AutoMapperModule', () => {
10055
let moduleFixture: TestingModule;
10156
let mapper: AutoMapper;
10257
let mapperMap: Map<string, AutoMapper>;
10358

10459
beforeAll(async () => {
10560
moduleFixture = await Test.createTestingModule({
106-
imports: [MockModule, MockSubModule],
61+
imports: [MockModule],
10762
}).compile();
63+
moduleFixture.get(AutomapperModule).onModuleInit();
10864
mapperMap = moduleFixture.get<Map<string, AutoMapper>>(MAPPER_MAP);
10965
mapper = moduleFixture.get<AutoMapper>(getMapperToken());
11066
});

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
"strictNullChecks": true,
1414
"strictFunctionTypes": true,
1515
"strictPropertyInitialization": true,
16+
"downlevelIteration": true,
1617
"noImplicitThis": true,
1718
"alwaysStrict": true,
18-
"noUnusedLocals": true,
19+
"noUnusedLocals": false,
1920
"noUnusedParameters": true,
2021
"noImplicitReturns": true,
2122
"noFallthroughCasesInSwitch": true,

0 commit comments

Comments
 (0)