Skip to content

Commit

Permalink
feat(core): add generic type for transformed value in decorators factory
Browse files Browse the repository at this point in the history
  • Loading branch information
quangtran88 committed Aug 27, 2023
1 parent 4eec6e5 commit cb6cc84
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 9 deletions.
32 changes: 23 additions & 9 deletions packages/core/services/reflector.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { uid } from 'uid';
/**
* @publicApi
*/
export interface CreateDecoratorOptions<T = any> {
export interface CreateDecoratorOptions<TParam = any, TTransformed = TParam> {
/**
* The key for the metadata.
* @default uid(21)
Expand All @@ -16,13 +16,21 @@ export interface CreateDecoratorOptions<T = any> {
* The transform function to apply to the metadata value.
* @default value => value
*/
transform?: (value: T) => T;
transform?: (value: TParam) => TTransformed;
}

type CreateDecoratorWithTransformOptions<
TParam,
TTransformed = TParam,
> = CreateDecoratorOptions<TParam, TTransformed> &
Required<Pick<CreateDecoratorOptions<TParam, TTransformed>, 'transform'>>;

/**
* @publicApi
*/
export type ReflectableDecorator<T> = ((opts?: T) => CustomDecorator) & {
export type ReflectableDecorator<TParam, TTransformedValue = TParam> = ((
opts?: TParam,
) => CustomDecorator) & {
KEY: string;
};

Expand All @@ -40,12 +48,18 @@ export class Reflector {
* @param options Decorator options.
* @returns A decorator function.
*/
static createDecorator<T>(
options: CreateDecoratorOptions = {},
): ReflectableDecorator<T> {
static createDecorator<TParam>(
options?: CreateDecoratorOptions<TParam>,
): ReflectableDecorator<TParam>;
static createDecorator<TParam, TTransformed>(
options: CreateDecoratorWithTransformOptions<TParam, TTransformed>,
): ReflectableDecorator<TParam, TTransformed>;
static createDecorator<TParam, TTransformed = TParam>(
options: CreateDecoratorOptions<TParam, TTransformed> = {},
): ReflectableDecorator<TParam, TTransformed> {
const metadataKey = options.key ?? uid(21);
const decoratorFn =
(metadataValue: T) =>
(metadataValue: TParam) =>
(target: object | Function, key?: string | symbol, descriptor?: any) => {
const value = options.transform
? options.transform(metadataValue)
Expand All @@ -54,7 +68,7 @@ export class Reflector {
};

decoratorFn.KEY = metadataKey;
return decoratorFn as ReflectableDecorator<T>;
return decoratorFn as ReflectableDecorator<TParam, TTransformed>;
}

/**
Expand All @@ -70,7 +84,7 @@ export class Reflector {
public get<T extends ReflectableDecorator<any>>(
decorator: T,
target: Type<any> | Function,
): T extends ReflectableDecorator<infer R> ? R : unknown;
): T extends ReflectableDecorator<any, infer R> ? R : unknown;
/**
* Retrieve metadata for a specified key for a specified target.
*
Expand Down
24 changes: 24 additions & 0 deletions packages/core/test/services/reflector.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { expect } from 'chai';
import { Reflector } from '../../services/reflector.service';

const transformDecorator = Reflector.createDecorator<string[], number>({
transform: value => value.length,
});

describe('Reflector', () => {
let reflector: Reflector;

class Test {}

@transformDecorator(['a', 'b', 'c'])
class TestTransform {}

beforeEach(() => {
reflector = new Reflector();
});

describe('get', () => {
it('should reflect metadata by key', () => {
const key = 'key';
const value = 'value';
Reflect.defineMetadata(key, value, Test);
expect(reflector.get(key, Test)).to.eql(value);
});

it('should reflect metadata by decorator', () => {
const decorator = Reflector.createDecorator<string>();
const value = 'value';
Expand All @@ -37,6 +48,19 @@ describe('Reflector', () => {
// @ts-expect-error 'value' is not assignable to parameter of type 'string[]'
reflectedValue = true;
});

it('should reflect metadata by decorator (with transform option)', () => {
let reflectedValue = reflector.get(transformDecorator, TestTransform);
expect(reflectedValue).to.eql(3);

// @ts-expect-error 'value' is not assignable to type 'number'
reflectedValue = [];
});

it('should require transform option when second generic type is provided', () => {
// @ts-expect-error Property 'transform' is missing in type {} but required in type
const decorator = Reflector.createDecorator<string[], number>({});
});
});

describe('getAll', () => {
Expand Down

0 comments on commit cb6cc84

Please sign in to comment.