Ошибки конфигурации TypeScript

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

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

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

Отсутствие этого флага приводит к тому, что синтаксис декораторов либо полностью игнорируется компилятором, либо вызывает ошибку компиляции в зависимости от версии TypeScript. В контексте class-validator это критично, так как вся система аннотаций (@IsString(), @Length(), @IsEmail()) построена именно на декораторах.

Важно учитывать, что даже при включённом experimentalDecorators поведение может различаться в зависимости от версии TypeScript. Несовместимость между версией компилятора и версией runtime-окружения часто проявляется в виде «тихо неработающих» валидаторов.

Метаданные типов и reflect-metadata

Вторая обязательная настройка — генерация метаданных типов:

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

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

Типичная ошибка проявляется в следующем виде: декораторы присутствуют, ошибок компиляции нет, но валидация всегда проходит успешно или возвращает пустые списки ошибок.

Дополнительно требуется импортировать polyfill:

import "reflect-metadata";

Его отсутствие приводит к тому, что глобальный объект Reflect не содержит методов getMetadata и defineMetadata, что делает невозможным хранение и чтение информации о типах.

Порядок импорта reflect-metadata

Критическим является порядок подключения:

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

Если reflect-metadata импортируется после объявления классов с декораторами, метаданные могут не быть зарегистрированы. Это проявляется особенно часто в проектах, где точка входа разбита на несколько модулей, и импорт выполняется не централизованно.

Конфигурация target и module

Параметры 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, и здесь возникают специфические проблемы:

  1. ts-node может не подхватывать tsconfig.json
  2. декораторы могут быть отключены в runtime-конфигурации
  3. несовпадение версий TypeScript между глобальной и локальной установкой

Особенно критично следующее: даже при правильном tsconfig.json, запуск без флага --compiler-options иногда приводит к игнорированию emitDecoratorMetadata.

Конфликты версий TypeScript и class-validator

Библиотека class-validator тесно связана с reflect-metadata и TypeScript AST. Несовместимость версий проявляется в следующих симптомах:

  • декораторы компилируются, но не выполняются
  • validate() возвращает пустой массив ошибок
  • частично работают только простые валидаторы (@IsString, @IsNumber), но не работают вложенные DTO

Такие проблемы часто возникают при использовании слишком новой версии TypeScript с устаревшей версией class-validator или наоборот.

Ошибки сборки в monorepo

В монорепозиториях (особенно с Yarn workspaces или pnpm) часто возникает ситуация, когда:

  • reflect-metadata установлен в одном пакете
  • DTO определены в другом
  • runtime контекст не видит глобальные метаданные

Результат — отсутствие валидации при корректной компиляции. Это связано с тем, что metadata storage привязан к конкретному экземпляру reflect-metadata, а не к процессу в целом.

Проблемы с tree-shaking и bundler’ами

При использовании современных сборщиков (Webpack, Vite, esbuild) может происходить удаление импорта:

import "reflect-metadata";

Если сборщик считает импорт «неиспользуемым», он может исключить его из финального бандла. В результате приложение запускается без поддержки метаданных.

Решение на уровне архитектуры — гарантировать side-effect import через entry-point файл, который не подвергается оптимизации.

Неправильная компиляция DTO классов

Частая ошибка — использование интерфейсов вместо классов:

interface UserDto {
  email: string;
}

class-validator работает только с классами, поскольку метаданные привязываются к runtime-структурам. При использовании интерфейсов после компиляции информация полностью исчезает.

Даже при правильной конфигурации TypeScript такая модель данных делает валидацию невозможной.

Ошибки в наследовании классов

При сложных иерархиях DTO возможны проблемы с наследованием метаданных:

class BaseDto {
  @IsString()
  id: string;
}

class UserDto extends BaseDto {
  @IsEmail()
  email: string;
}

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

Несовместимость с ES Modules

При использовании "type": "module" в package.json возникают дополнительные сложности:

  • необходимость явного расширения файлов .js в импортах
  • нестабильная работа reflect-metadata в некоторых конфигурациях Node.js
  • различия между CommonJS и ESM загрузкой классов

В таких условиях class-validator может «терять» метаданные при динамическом импорте модулей.

Ошибки при транспиляции без TypeScript

В проектах, где используется 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 build и cache

При включённом incremental компиляция может использовать устаревшие .tsbuildinfo, содержащие некорректные данные о декораторах. Это приводит к ситуации, когда код уже исправлен, но runtime поведение остаётся старым.

Удаление кэша сборки часто выявляет скрытые проблемы конфигурации, которые невозможно заметить при обычной пересборке.