- Published on
TS 5.5 데코레이터에서 Unable to resolve signature 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버나 프레임워크 코드를 TS 5.5로 올리면서 데코레이터를 쓰는 순간, 빌드가 아래 형태로 깨지는 경우가 많습니다.
Unable to resolve signature of method decorator when called as an expressionUnable to resolve signature of parameter decorator when called as an expressionUnable to resolve signature of property decorator when called as an expression
겉으로는 “데코레이터 타입이 안 맞는다”는 얘기지만, TS 5.5에서 특히 많이 터지는 이유는 데코레이터가 두 계열(레거시/스탠다드)로 나뉘고, 프로젝트 설정 및 라이브러리(예: NestJS, TypeORM, class-validator 등)가 요구하는 시그니처가 서로 다르기 때문입니다. 이 글에서는 원인을 빠르게 분류하고, 각 케이스별로 안전하게 고치는 패턴을 제시합니다.
참고로 TS 5.x에서의 타입 안정성 점검 흐름이 바뀌면서(예: satisfies 활용) “컴파일은 되는데 타입이 미묘하게 깨지는” 상황도 자주 보이니, 관련 글인 TS 5.x satisfies로 타입 깨짐 잡는 법도 함께 보면 전체적인 감이 잡힙니다.
1) 가장 먼저 확인할 것: 레거시 데코레이터인지, 스탠다드 데코레이터인지
TS에서 데코레이터는 크게 두 세계가 있습니다.
- 레거시(Experimental) 데코레이터:
experimentalDecorators기반. 기존 생태계(특히 NestJS/TypeORM 등)에서 널리 사용. - 스탠다드(Stage 3) 데코레이터: 최신 표준 시그니처(컨텍스트 객체 기반). TS 5.x에서 점점 표준 쪽으로 이동.
문제는, 라이브러리가 레거시 시그니처를 기대하는데 프로젝트가 스탠다드 쪽으로 해석하거나(혹은 그 반대), 직접 만든 데코레이터가 다른 세계의 타입을 섞어 쓰면 Unable to resolve signature가 터진다는 점입니다.
체크리스트
tsconfig.json에experimentalDecorators가 켜져 있나emitDecoratorMetadata가 필요한 라이브러리인데 꺼져 있진 않나- 데코레이터 타입을
MethodDecorator,PropertyDecorator,ParameterDecorator같은 레거시 타입으로 선언해놓고, 실제 구현은 스탠다드 시그니처로 작성하진 않았나
2) tsconfig 설정: 레거시 생태계라면 이 조합이 기본
NestJS/TypeORM/class-transformer/class-validator 등 “레거시 데코레이터 기반” 라이브러리를 쓴다면, 대개 아래 조합이 가장 무난합니다.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"useDefineForClassFields": false
}
}
왜 useDefineForClassFields가 영향을 주나
필드 초기화 방식이 define 기반으로 바뀌면, 일부 데코레이터 기반 라이브러리(특히 런타임 메타데이터를 기대하는 쪽)에서 동작/타이밍 문제가 생길 수 있습니다. 에러가 “시그니처를 해석 못한다”로만 나오더라도, 실제로는 설정 충돌이 원인인 경우가 있어 함께 점검합니다.
3) 가장 흔한 직접 원인: 데코레이터 팩토리의 반환 타입이 너무 좁다
예를 들어 메서드 데코레이터를 만든다고 했을 때, 아래처럼 “그냥 함수 반환”으로 두면 TS가 추론을 잘 못하는 케이스가 있습니다.
문제 예시
export function Log() {
return (target: any, key: string, desc: PropertyDescriptor) => {
const original = desc.value;
desc.value = function (...args: unknown[]) {
console.log("call", key, args);
return original.apply(this, args);
};
};
}
이 코드는 얼핏 정상 같지만, 프로젝트 설정/엄격도/타입 정의에 따라 Unable to resolve signature of method decorator...가 발생할 수 있습니다. 이유는 TS가 “이 반환 함수가 정말 MethodDecorator 규약을 만족하는지”를 확정하지 못하는 순간이 생기기 때문입니다.
해결: 반환 타입을 MethodDecorator로 고정
export function Log(): MethodDecorator {
return (target, propertyKey, descriptor) => {
if (!descriptor || typeof descriptor.value !== "function") return;
const original = descriptor.value;
descriptor.value = function (...args: unknown[]) {
console.log("call", String(propertyKey), args);
return original.apply(this, args);
};
};
}
핵심은 두 가지입니다.
- 데코레이터 팩토리 반환 타입을
MethodDecorator로 명시 descriptor가undefined일 수 있는 경로를 방어(특히 오버로드/추상/접근자 등에서 꼬일 때)
4) 오버로드/제네릭/접근자에서 잘 터지는 패턴과 방어법
TS는 메서드 오버로드가 있거나, 접근자(get/set) 데코레이터를 섞거나, 제네릭 메서드에 데코레이터를 붙이면 “어떤 시그니처에 적용되는지”를 더 엄격하게 따집니다. 이때 반환 타입이 불명확하면 오류가 잘 납니다.
오버로드 메서드에 데코레이터를 붙이는 경우
class Service {
@Log()
run(x: string): string;
run(x: number): number;
run(x: string | number) {
return x;
}
}
이 경우 데코레이터는 실제 구현 시그니처에만 적용되지만, 타입 시스템은 오버로드 시그니처들과의 관계를 계산합니다.
권장 해결
- 데코레이터 내부에서
descriptor.value가 함수인지 확인 descriptor.value를 감싼 뒤에도 원래 타입을 최대한 유지
export function Log(): MethodDecorator {
return (_target, propertyKey, descriptor) => {
const value = descriptor?.value;
if (typeof value !== "function") return;
descriptor.value = function (...args: unknown[]) {
console.log("call", String(propertyKey));
return value.apply(this, args);
};
};
}
타입을 완벽히 보존하려면 제네릭으로 함수 타입을 캡처해야 하지만, 레거시 MethodDecorator 타입 자체가 이를 강하게 표현하기 어렵습니다. 대신 “런타임 안전성 + 최소한의 타입 힌트”를 확보하는 방향이 실전에서 안정적입니다.
5) 파라미터 데코레이터에서의 오류: propertyKey와 parameterIndex 타입을 맞춰라
파라미터 데코레이터는 메서드 파라미터/생성자 파라미터 모두에 붙을 수 있고, propertyKey가 undefined가 될 수 있습니다(생성자 파라미터).
문제 예시
export function FromBody() {
return (target: any, key: string, index: number) => {
// ...
};
}
여기서 key를 string으로 고정해버리면, 생성자 파라미터에서 undefined가 올 수 있는 가능성과 충돌해 시그니처 해석이 실패할 수 있습니다.
해결: ParameterDecorator로 반환 타입 고정 + propertyKey를 넓게
export function FromBody(): ParameterDecorator {
return (target, propertyKey, parameterIndex) => {
// propertyKey는 string | symbol | undefined 가능
console.log("param", String(propertyKey), parameterIndex);
};
}
6) 프로퍼티 데코레이터에서의 오류: 필드 데코레이터는 반환값이 없다
레거시 프로퍼티 데코레이터는 반환값을 사용하지 않습니다. 그런데 종종 아래처럼 값을 반환하거나, PropertyDescriptor를 받는 형태로 착각해서 구현하면 시그니처가 어긋납니다.
잘못된 예시
export function Required() {
return (target: any, key: string, desc: PropertyDescriptor) => {
// PropertyDecorator에는 desc가 없음
};
}
올바른 예시
export function Required(): PropertyDecorator {
return (target, propertyKey) => {
// 메타데이터 저장 등
console.log("required", target.constructor.name, String(propertyKey));
};
}
7) TS 5.5에서 특히 자주 보는 케이스: 라이브러리 타입 정의가 스탠다드 데코레이터 기준으로 바뀐 경우
일부 라이브러리는 내부 타입 정의가 스탠다드 데코레이터 쪽으로 이동하거나, 두 버전을 동시에 지원하기 위해 타입이 복잡해졌습니다. 이때 프로젝트가 레거시 데코레이터로 컴파일 중인데, 데코레이터 함수 타입을 스탠다드처럼 작성하면 충돌합니다.
증상
- 예전 TS 버전에서는 통과
- TS 5.5로 올린 뒤 특정 데코레이터만
Unable to resolve signature - 특히
ClassDecoratorContext같은 컨텍스트 타입이 언급되거나, 데코레이터 인자 개수가 맞지 않는다는 식의 오류가 동반
실전 처방
- 먼저 프로젝트가 레거시 생태계라면
experimentalDecorators: true를 확실히 켭니다. - 직접 구현한 데코레이터는 레거시 타입(
ClassDecorator,MethodDecorator등)으로 반환 타입을 명시합니다. - 라이브러리 데코레이터에서만 터지면, 해당 라이브러리의 버전 릴리즈 노트에서 “decorators” 관련 변경을 확인하고 버전을 맞춥니다.
8) 디버깅을 빠르게 하는 최소 재현 템플릿
오류가 복잡한 프로젝트에서만 나면, 아래처럼 최소 재현을 만들어 “어떤 데코레이터 타입에서 깨지는지” 먼저 분리하는 게 빠릅니다.
// decorator.ts
export function D(): MethodDecorator {
return (_t, _k, d) => {
if (!d) return;
};
}
// index.ts
class A {
@D()
m() {}
}
여기서도 깨진다면 설정 문제일 가능성이 높고, 여기서는 멀쩡한데 실프로젝트에서만 깨지면 “오버로드/접근자/제네릭/라이브러리 타입” 중 하나로 좁혀갈 수 있습니다.
9) 팀 차원의 예방: 데코레이터 팩토리에 satisfies로 의도 고정
TS 5.x에서 satisfies는 “추론은 유지하면서도, 특정 인터페이스/타입을 만족하는지 검증”할 때 유용합니다. 데코레이터도 마찬가지로, 반환 함수가 레거시 시그니처를 만족하는지 더 명확히 검증할 수 있습니다.
export function Log() {
const decorator = ((target, propertyKey, descriptor) => {
if (!descriptor || typeof descriptor.value !== "function") return;
const original = descriptor.value;
descriptor.value = function (...args: unknown[]) {
console.log("call", String(propertyKey), args);
return original.apply(this, args);
};
}) satisfies MethodDecorator;
return decorator;
}
이 패턴은 “반환 타입을 강제로 MethodDecorator로 캐스팅”하는 것보다 안전합니다. 캐스팅은 타입 오류를 숨기지만, satisfies는 타입 불일치를 드러내기 때문입니다. 더 자세한 배경은 TS 5.x satisfies로 타입 깨짐 잡는 법에서 확장해서 볼 수 있습니다.
10) 결론: 오류 메시지보다 “세계관”을 먼저 맞춰라
Unable to resolve signature는 표면적으로는 타입 문제지만, TS 5.5에서는 특히 다음 순서로 접근하면 해결이 빠릅니다.
- 프로젝트가 레거시 데코레이터 생태계인지 확인하고
experimentalDecorators를 켠다. - 데코레이터 팩토리 반환 타입을
ClassDecorator/MethodDecorator/PropertyDecorator/ParameterDecorator로 명시한다. - 파라미터 데코레이터의
propertyKey가undefined일 수 있음을 반영한다. - 오버로드/접근자/제네릭 메서드에서
descriptor방어 코드를 넣는다. - 가능하면
satisfies로 “의도한 시그니처 만족”을 컴파일 타임에 고정한다.
이 다섯 가지만 적용해도 TS 5.5 업그레이드에서 데코레이터 관련 컴파일 오류의 대부분은 정리됩니다.