Включение experimentalDecorators и emitDecoratorMetadata

Библиотека class-validator основана на механизме декораторов TypeScript и метаданных, которые компилятор может генерировать на этапе трансляции кода. Без корректной настройки компилятора использование большинства возможностей библиотеки становится невозможным или сильно ограниченным. Ключевую роль здесь играют два параметра конфигурации TypeScript: experimentalDecorators и emitDecoratorMetadata.


Включение поддержки декораторов в TypeScript

Декораторы — это специальный синтаксис, позволяющий модифицировать классы, их свойства и методы. В контексте class-validator они используются для объявления правил валидации прямо в модели данных.

По умолчанию TypeScript не включает поддержку декораторов, поскольку они долгое время находились в стадии предложения (proposal stage) для JavaScript. Поэтому их необходимо явно активировать.

Для этого в файле tsconfig.json задаётся параметр:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

После включения этого флага компилятор начинает распознавать конструкции вида:

import { IsString } from "class-validator";

class User {
  @IsString()
  name: string;
}

Без experimentalDecorators такой код приведёт к ошибке компиляции, так как символ @ будет считаться недопустимым синтаксисом.


Генерация метаданных типов

Одного включения декораторов недостаточно для полноценной работы class-validator. Библиотека опирается на механизм рефлексии типов, предоставляемый через пакет reflect-metadata. Для того чтобы TypeScript генерировал информацию о типах свойств классов, необходимо включить параметр:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true
  }
}

Этот флаг заставляет компилятор добавлять в сгенерированный JavaScript метаданные о типах параметров и свойств, которые затем могут быть прочитаны во время выполнения.


Связь с reflect-metadata

Метаданные, генерируемые TypeScript, сами по себе не доступны без специального API. Для их чтения используется библиотека reflect-metadata, которая полифилит предложение ECMAScript Metadata Reflection API.

Её необходимо импортировать один раз на уровне приложения, обычно в точке входа:

import "reflect-metadata";

Без этого импорта декораторы могут применяться, но попытки получить типы через class-validator завершатся отсутствием данных о типах или ошибками валидации.


Полная конфигурация tsconfig.json для class-validator

Корректная работа библиотеки требует согласованной конфигурации компилятора. Минимально необходимый набор опций выглядит следующим образом:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strict": true
  }
}

Каждый параметр играет свою роль:

  • target — определяет версию JavaScript, в которую компилируется код. Желательно использовать ES2017 и выше.
  • module — система модулей (CommonJS или ESModules), влияющая на способ загрузки reflect-metadata.
  • experimentalDecorators — активирует синтаксис декораторов.
  • emitDecoratorMetadata — включает генерацию runtime-метаданных.
  • strict — усиливает типизацию, что косвенно снижает количество ошибок валидации.

Как работает генерация метаданных

При включённом emitDecoratorMetadata TypeScript добавляет к классам специальные вызовы Reflect.metadata. Например, следующий код:

class User {
  name: string;
}

при компиляции превращается в нечто подобное:

__decorate([
  Reflect.metadata("design:type", String)
], User.prototype, "name", void 0);

Эти данные затем используются class-validator и class-transformer для определения типа свойства name без необходимости явного указания.


Роль design:type и других метаданных

TypeScript генерирует несколько ключевых типов метаданных:

  • design:type — тип свойства (String, Number, Boolean, класс и т.д.)
  • design:paramtypes — типы параметров конструктора или метода
  • design:returntype — тип возвращаемого значения метода

В контексте class-validator наиболее важным является design:type, так как он позволяет библиотеке понимать, какие валидаторы применять по умолчанию или как интерпретировать вложенные объекты.


Типичные ошибки при неправильной конфигурации

Отсутствие корректной настройки приводит к ряду характерных проблем:

1. Валидаторы не срабатывают

Класс выглядит корректно:

class User {
  @IsString()
  name: string;
}

но при вызове validate() ошибки не возвращаются. Причина — отсутствуют метаданные типов.


2. undefined в типах вложенных объектов

При использовании вложенных DTO:

class Profile {
  @IsString()
  bio: string;
}

class User {
  @ValidateNested()
  profile: Profile;
}

без emitDecoratorMetadata библиотека не может определить тип Profile, и валидация вложенных объектов не выполняется.


3. Ошибки Reflect is not defined

Если не подключён reflect-metadata, возникает ошибка выполнения:

ReferenceError: Reflect is not defined

или:

Reflect.getMetadata is not a function

Важность порядка импорта

Импорт reflect-metadata должен выполняться до использования любых декораторов. Обычно он размещается в самом начале входного файла:

import "reflect-metadata";
import { validate } from "class-validator";

Если импорт выполняется после объявления классов, метаданные могут не быть зарегистрированы корректно.


Влияние сборщиков и рантайма

Использование разных инструментов сборки может влиять на работу декораторов:

  • ts-node требует включённых флагов в tsconfig.json, иначе runtime не создаёт метаданные
  • Webpack должен корректно обрабатывать TypeScript с включёнными декораторами
  • Babel требует отдельной настройки @babel/plugin-proposal-decorators и @babel/plugin-proposal-class-properties, иначе поведение отличается от TypeScript

Особенно важно учитывать, что Babel по умолчанию не генерирует emitDecoratorMetadata, что делает его несовместимым с class-validator без дополнительных плагинов.


Отличия старого и нового режима декораторов

TypeScript поддерживает два режима декораторов:

  • legacy (используется experimentalDecorators: true)
  • stage 3 decorators (новая спецификация ECMAScript)

class-validator до сих пор ориентируется на legacy-реализацию, поскольку именно она стабильно поддерживает генерацию reflect-metadata. При использовании нового стандарта возможны несовместимости, особенно в части метаданных типов.


Особенности использования в monorepo и больших проектах

В крупных проектах часто возникает ситуация, когда разные пакеты используют разные tsconfig. Это приводит к тому, что:

  • в одном пакете метаданные генерируются
  • в другом — отсутствуют
  • в рантайме поведение class-validator становится непредсказуемым

Для устранения проблемы необходимо обеспечить единый базовый tsconfig, от которого наследуются все пакеты, с обязательным включением:

  • experimentalDecorators
  • emitDecoratorMetadata

Проверка корректности включения метаданных

Проверить, что метаданные действительно генерируются, можно через простой тест:

import "reflect-metadata";

class Test {
  value: string;
}

const type = Reflect.getMetadata("design:type", Test.prototype, "value");
console.log(type);

Ожидаемый результат:

[String: String]

Если вывод undefined, значит emitDecoratorMetadata не работает или код не проходит через TypeScript-компилятор.


Влияние строгой типизации на поведение class-validator

При включённом strict режиме TypeScript усиливает проверку типов, что снижает количество ситуаций, когда runtime-тип не совпадает с compile-time типом. Это особенно важно в связке с class-validator, поскольку библиотека работает на стыке статической и динамической типизации.

Например, при строгой настройке:

name: string;

TypeScript не позволит случайно присвоить number, а значит валидатор будет работать с более предсказуемыми данными.


Использование в сочетании с NestJS

В экосистеме NestJS эти параметры включаются автоматически в шаблонах проектов, поскольку фреймворк активно использует class-validator для DTO-валидации. В таких проектах наличие:

"experimentalDecorators": true,
"emitDecoratorMetadata": true

является обязательным условием корректной работы пайплайна валидации запросов.


Ограничения метаданных TypeScript

Несмотря на полезность, механизм имеет ограничения:

  • не сохраняются сложные union-типы
  • отсутствует информация о generics
  • runtime-тип часто упрощается до базового конструктора
  • интерфейсы полностью исчезают после компиляции

Это означает, что class-validator не может полагаться на интерфейсы TypeScript и требует именно классовую структуру данных.


Итоговая роль двух флагов в архитектуре валидации

experimentalDecorators отвечает за возможность описания правил валидации декларативно через @decorator.

emitDecoratorMetadata обеспечивает передачу информации о типах в runtime, позволяя библиотеке автоматически определять структуру данных.

Совместная работа этих механизмов формирует основу, на которой строится вся модель валидации class-validator в TypeScript-приложениях.