Skip to content

Commit 9db024d

Browse files
authored
fix(nestjs): Copy metadata in custom decorators (#15598)
- adds functionality for copying metadata in our custom decorators - applies this to all our decorators, not just `SentryTraced` - unit tests closes #14384
1 parent 5bf4598 commit 9db024d

File tree

4 files changed

+422
-9
lines changed

4 files changed

+422
-9
lines changed

packages/nestjs/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
},
5555
"devDependencies": {
5656
"@nestjs/common": "^10.0.0",
57-
"@nestjs/core": "^10.0.0"
57+
"@nestjs/core": "^10.0.0",
58+
"reflect-metadata": "^0.2.2"
5859
},
5960
"peerDependencies": {
6061
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",

packages/nestjs/src/decorators.ts

+39-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export const SentryCron = (monitorSlug: string, monitorConfig?: MonitorConfig):
2020
monitorConfig,
2121
);
2222
};
23+
24+
copyFunctionNameAndMetadata({ originalMethod, descriptor });
25+
2326
return descriptor;
2427
};
2528
};
@@ -28,7 +31,7 @@ export const SentryCron = (monitorSlug: string, monitorConfig?: MonitorConfig):
2831
* A decorator usable to wrap arbitrary functions with spans.
2932
*/
3033
export function SentryTraced(op: string = 'function') {
31-
return function (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) {
34+
return function (_target: unknown, propertyKey: string, descriptor: PropertyDescriptor) {
3235
const originalMethod = descriptor.value as (...args: unknown[]) => Promise<unknown> | unknown; // function can be sync or async
3336

3437
descriptor.value = function (...args: unknown[]) {
@@ -43,13 +46,7 @@ export function SentryTraced(op: string = 'function') {
4346
);
4447
};
4548

46-
// preserve the original name on the decorated function
47-
Object.defineProperty(descriptor.value, 'name', {
48-
value: originalMethod.name,
49-
configurable: true,
50-
enumerable: true,
51-
writable: true,
52-
});
49+
copyFunctionNameAndMetadata({ originalMethod, descriptor });
5350

5451
return descriptor;
5552
};
@@ -71,6 +68,40 @@ export function SentryExceptionCaptured() {
7168
return originalCatch.apply(this, [exception, host, ...args]);
7269
};
7370

71+
copyFunctionNameAndMetadata({ originalMethod: originalCatch, descriptor });
72+
7473
return descriptor;
7574
};
7675
}
76+
77+
/**
78+
* Copies the function name and metadata from the original method to the decorated method.
79+
* This ensures that the decorated method maintains the same name and metadata as the original.
80+
*
81+
* @param {Function} params.originalMethod - The original method being decorated
82+
* @param {PropertyDescriptor} params.descriptor - The property descriptor containing the decorated method
83+
*/
84+
function copyFunctionNameAndMetadata({
85+
originalMethod,
86+
descriptor,
87+
}: {
88+
descriptor: PropertyDescriptor;
89+
originalMethod: (...args: unknown[]) => unknown;
90+
}): void {
91+
// preserve the original name on the decorated function
92+
Object.defineProperty(descriptor.value, 'name', {
93+
value: originalMethod.name,
94+
configurable: true,
95+
enumerable: true,
96+
writable: true,
97+
});
98+
99+
// copy metadata
100+
if (typeof Reflect !== 'undefined' && typeof Reflect.getMetadataKeys === 'function') {
101+
const originalMetaData = Reflect.getMetadataKeys(originalMethod);
102+
for (const key of originalMetaData) {
103+
const value = Reflect.getMetadata(key, originalMethod);
104+
Reflect.defineMetadata(key, value, descriptor.value);
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)