Неверные настройки компилятора TypeScript являются одной из самых частых причин некорректной работы декораторов и, как следствие, полной или частичной неработоспособности механизмов валидации, основанных на метаданных. Библиотека class-validator опирается на runtime-информацию, которую TypeScript по умолчанию не генерирует, и любые отклонения в конфигурации приводят к ошибкам, которые сложно диагностировать на уровне приложения.
Одним из обязательных условий корректной работы является активация поддержки декораторов:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Отсутствие этого флага приводит к тому, что синтаксис декораторов
либо полностью игнорируется компилятором, либо вызывает ошибку
компиляции в зависимости от версии TypeScript. В контексте
class-validator это критично, так как вся система аннотаций
(@IsString(), @Length(),
@IsEmail()) построена именно на декораторах.
Важно учитывать, что даже при включённом
experimentalDecorators поведение может различаться в
зависимости от версии TypeScript. Несовместимость между версией
компилятора и версией runtime-окружения часто проявляется в виде «тихо
неработающих» валидаторов.
Вторая обязательная настройка — генерация метаданных типов:
{
"compilerOptions": {
"emitDecoratorMetadata": true
}
}
Эта опция обеспечивает генерацию информации о типах полей, которая
используется class-validator совместно с reflect-metadata.
Без неё валидатор не может определить, какой тип данных ожидается, и
многие проверки либо не выполняются, либо работают некорректно.
Типичная ошибка проявляется в следующем виде: декораторы присутствуют, ошибок компиляции нет, но валидация всегда проходит успешно или возвращает пустые списки ошибок.
Дополнительно требуется импортировать polyfill:
import "reflect-metadata";
Его отсутствие приводит к тому, что глобальный объект Reflect не
содержит методов getMetadata и defineMetadata,
что делает невозможным хранение и чтение информации о типах.
Критическим является порядок подключения:
import "reflect-metadata";
import { validate } from "class-validator";
Если reflect-metadata импортируется после объявления
классов с декораторами, метаданные могут не быть зарегистрированы. Это
проявляется особенно часто в проектах, где точка входа разбита на
несколько модулей, и импорт выполняется не централизованно.
Параметры target и module влияют на
трансформацию декораторов:
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs"
}
}
При слишком старом target TypeScript может компилировать
декораторы в несовместимую форму, что приводит к потере метаданных. В
современных версиях рекомендуется использовать как минимум
ES2017 или выше, поскольку это обеспечивает корректную
работу runtime-рефлексии.
Несовпадение module (например, использование
ESNext в Node.js без соответствующей настройки загрузчика)
может приводить к тому, что reflect-metadata не загружается
в глобальный контекст, особенно в ESM-проектах.
Режим strict сам по себе не ломает class-validator, но
выявляет скрытые несоответствия типов:
{
"compilerOptions": {
"strict": true
}
}
На практике проблемы возникают при сочетании строгой типизации и
отсутствия явных преобразований типов. Например, входные данные из
HTTP-запросов приходят как string, даже если поле объявлено
как number. Валидация может проходить формально, но
логически данные остаются некорректными.
Особое внимание требуется опции:
strictPropertyInitializationЕсли она включена, классы DTO без инициализации свойств начинают
требовать явных значений, что часто приводит к необходимости добавления
! или дефолтных значений, иначе TypeScript выдаёт
ошибки.
В средах разработки часто используется ts-node, и здесь
возникают специфические проблемы:
ts-node может не подхватывать
tsconfig.jsonОсобенно критично следующее: даже при правильном
tsconfig.json, запуск без флага
--compiler-options иногда приводит к игнорированию
emitDecoratorMetadata.
Библиотека class-validator тесно связана с reflect-metadata и TypeScript AST. Несовместимость версий проявляется в следующих симптомах:
validate() возвращает пустой массив ошибок@IsString,
@IsNumber), но не работают вложенные DTOТакие проблемы часто возникают при использовании слишком новой версии TypeScript с устаревшей версией class-validator или наоборот.
В монорепозиториях (особенно с Yarn workspaces или pnpm) часто возникает ситуация, когда:
reflect-metadata установлен в одном пакетеРезультат — отсутствие валидации при корректной компиляции. Это
связано с тем, что metadata storage привязан к конкретному экземпляру
reflect-metadata, а не к процессу в целом.
При использовании современных сборщиков (Webpack, Vite, esbuild) может происходить удаление импорта:
import "reflect-metadata";
Если сборщик считает импорт «неиспользуемым», он может исключить его из финального бандла. В результате приложение запускается без поддержки метаданных.
Решение на уровне архитектуры — гарантировать side-effect import через entry-point файл, который не подвергается оптимизации.
Частая ошибка — использование интерфейсов вместо классов:
interface UserDto {
email: string;
}
class-validator работает только с классами, поскольку метаданные привязываются к runtime-структурам. При использовании интерфейсов после компиляции информация полностью исчезает.
Даже при правильной конфигурации TypeScript такая модель данных делает валидацию невозможной.
При сложных иерархиях DTO возможны проблемы с наследованием метаданных:
class BaseDto {
@IsString()
id: string;
}
class UserDto extends BaseDto {
@IsEmail()
email: string;
}
Если emitDecoratorMetadata отключён или частично не
работает, метаданные базового класса могут не объединяться с
наследником, что приводит к неполной валидации.
При использовании "type": "module" в package.json
возникают дополнительные сложности:
.js в
импортахВ таких условиях class-validator может «терять» метаданные при динамическом импорте модулей.
В проектах, где используется Babel вместо TypeScript для
транспиляции, часто отсутствует генерация metadata вообще. Babel по
умолчанию не эмитит design:type, если не подключён
соответствующий плагин:
@babel/plugin-proposal-decorators@babel/plugin-proposal-class-propertiesБез них class-validator получает «пустые» классы без типовой информации, что делает большинство валидаторов неэффективными.
Типичный симптом неправильной конфигурации:
validate() всегда возвращает []Это почти всегда указывает на отсутствие
emitDecoratorMetadata или неправильную загрузку
reflect-metadata.
Дополнительный способ проверки — вывод метаданных вручную:
import "reflect-metadata";
console.log(Reflect.getMetadata("design:type", User.prototype, "email"));
Если результат undefined, значит цепочка генерации
метаданных нарушена.
При включённом incremental компиляция может использовать
устаревшие .tsbuildinfo, содержащие некорректные данные о
декораторах. Это приводит к ситуации, когда код уже исправлен, но
runtime поведение остаётся старым.
Удаление кэша сборки часто выявляет скрытые проблемы конфигурации, которые невозможно заметить при обычной пересборке.