Skip to content

Commit 65ba126

Browse files
committed
feat(core): add generic type for transformed value in decorators factory
1 parent 4eec6e5 commit 65ba126

File tree

2 files changed

+47
-9
lines changed

2 files changed

+47
-9
lines changed

packages/core/services/reflector.service.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { uid } from 'uid';
55
/**
66
* @publicApi
77
*/
8-
export interface CreateDecoratorOptions<T = any> {
8+
export interface CreateDecoratorOptions<TParam = any, TTransformed = TParam> {
99
/**
1010
* The key for the metadata.
1111
* @default uid(21)
@@ -16,13 +16,21 @@ export interface CreateDecoratorOptions<T = any> {
1616
* The transform function to apply to the metadata value.
1717
* @default value => value
1818
*/
19-
transform?: (value: T) => T;
19+
transform?: (value: TParam) => TTransformed;
2020
}
2121

22+
type CreateDecoratorWithTransformOptions<
23+
TParam,
24+
TTransformed = TParam,
25+
> = CreateDecoratorOptions<TParam, TTransformed> &
26+
Required<Pick<CreateDecoratorOptions<TParam, TTransformed>, 'transform'>>;
27+
2228
/**
2329
* @publicApi
2430
*/
25-
export type ReflectableDecorator<T> = ((opts?: T) => CustomDecorator) & {
31+
export type ReflectableDecorator<TParam, TTransformed = TParam> = ((
32+
opts?: TParam,
33+
) => CustomDecorator) & {
2634
KEY: string;
2735
};
2836

@@ -40,12 +48,18 @@ export class Reflector {
4048
* @param options Decorator options.
4149
* @returns A decorator function.
4250
*/
43-
static createDecorator<T>(
44-
options: CreateDecoratorOptions = {},
45-
): ReflectableDecorator<T> {
51+
static createDecorator<TParam>(
52+
options?: CreateDecoratorOptions<TParam>,
53+
): ReflectableDecorator<TParam>;
54+
static createDecorator<TParam, TTransformed>(
55+
options: CreateDecoratorWithTransformOptions<TParam, TTransformed>,
56+
): ReflectableDecorator<TParam, TTransformed>;
57+
static createDecorator<TParam, TTransformed = TParam>(
58+
options: CreateDecoratorOptions<TParam, TTransformed> = {},
59+
): ReflectableDecorator<TParam, TTransformed> {
4660
const metadataKey = options.key ?? uid(21);
4761
const decoratorFn =
48-
(metadataValue: T) =>
62+
(metadataValue: TParam) =>
4963
(target: object | Function, key?: string | symbol, descriptor?: any) => {
5064
const value = options.transform
5165
? options.transform(metadataValue)
@@ -54,7 +68,7 @@ export class Reflector {
5468
};
5569

5670
decoratorFn.KEY = metadataKey;
57-
return decoratorFn as ReflectableDecorator<T>;
71+
return decoratorFn as ReflectableDecorator<TParam, TTransformed>;
5872
}
5973

6074
/**
@@ -70,7 +84,7 @@ export class Reflector {
7084
public get<T extends ReflectableDecorator<any>>(
7185
decorator: T,
7286
target: Type<any> | Function,
73-
): T extends ReflectableDecorator<infer R> ? R : unknown;
87+
): T extends ReflectableDecorator<any, infer R> ? R : unknown;
7488
/**
7589
* Retrieve metadata for a specified key for a specified target.
7690
*

packages/core/test/services/reflector.service.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
import { expect } from 'chai';
22
import { Reflector } from '../../services/reflector.service';
33

4+
const transformDecorator = Reflector.createDecorator<string[], number>({
5+
transform: value => value.length,
6+
});
7+
48
describe('Reflector', () => {
59
let reflector: Reflector;
10+
611
class Test {}
12+
13+
@transformDecorator(['a', 'b', 'c'])
14+
class TestTransform {}
15+
716
beforeEach(() => {
817
reflector = new Reflector();
918
});
19+
1020
describe('get', () => {
1121
it('should reflect metadata by key', () => {
1222
const key = 'key';
1323
const value = 'value';
1424
Reflect.defineMetadata(key, value, Test);
1525
expect(reflector.get(key, Test)).to.eql(value);
1626
});
27+
1728
it('should reflect metadata by decorator', () => {
1829
const decorator = Reflector.createDecorator<string>();
1930
const value = 'value';
@@ -37,6 +48,19 @@ describe('Reflector', () => {
3748
// @ts-expect-error 'value' is not assignable to parameter of type 'string[]'
3849
reflectedValue = true;
3950
});
51+
52+
it('should reflect metadata by decorator (with transform option)', () => {
53+
let reflectedValue = reflector.get(transformDecorator, TestTransform);
54+
expect(reflectedValue).to.eql(3);
55+
56+
// @ts-expect-error 'value' is not assignable to type 'number'
57+
reflectedValue = [];
58+
});
59+
60+
it('should require transform option when second generic type is provided', () => {
61+
// @ts-expect-error Property 'transform' is missing in type {} but required in type
62+
const decorator = Reflector.createDecorator<string[], number>({});
63+
});
4064
});
4165

4266
describe('getAll', () => {

0 commit comments

Comments
 (0)