Как используешь метаданные в TypeScript/Node.js?

Ответ

В контексте TypeScript и Node.js под метаданными я понимаю информацию о типах, структурах или конфигурации, которая доступна во время выполнения (runtime) или используется инструментами на этапе компиляции.

1. Метаданные TypeScript (декораторы и reflect-metadata): Это мощный механизм для добавления информации к классам, методам или свойствам, которую потом можно прочитать.

import 'reflect-metadata';

// Декоратор, который добавляет метаданные
function Log(target: any, key: string) {
  const types = Reflect.getMetadata('design:type', target, key);
  const paramtypes = Reflect.getMetadata('design:paramtypes', target, key);
  console.log(`${key} type: ${types.name}, param types: ${paramtypes?.map(p => p.name)}`);
}

class MyService {
  @Log
  greet(name: string): string {
    return `Hello, ${name}`;
  }
}
// При запуске (с включенной опцией `emitDecoratorMetadata` в tsconfig.json)
// в консоль выведется: greet type: Function, param types: String

Где это применяется:

  • Фреймворки: NestJS, TypeORM, class-validator активно используют декораторы и метаданные для dependency injection, описания моделей БД и валидации.
  • Собственные валидаторы или сериализаторы: Можно создать декоратор @MaxLength(10) и в runtime проверить значение поля.

2. Метаданные пакетов (package.json): В Node.js package.json — это основные метаданные модуля. Я использую их для:

{
  "name": "my-service",
  "version": "1.0.0",
  "engines": { "node": ">=18.0.0" }, // Метаданные о среде
  "config": { "defaultPort": 3000 } // Кастомная конфигурация
}

Доступ через process.env.npm_package_config_defaultPort.

3. Метаданные в HTTP-запросах/ответах: Заголовки (X-Request-Id, User-Agent) — это форма метаданных для отслеживания и логирования.

app.use((req, res, next) => {
  req.id = uuid.v4(); // Добавляем метаданные к объекту запроса
  res.setHeader('X-Response-Time', Date.now() - req.startTime);
  next();
});

Ответ 18+ 🔞

Вот, смотри, объясняю про метаданные в TypeScript и Node.js, как есть. Это же просто информация о твоём коде или приложении, которая болтается где-то рядом — либо когда код уже работает, либо когда компилятор его жуёт. Типа справки о самом себе.

1. Метаданные TypeScript (эти ваши декораторы и reflect-metadata): Ёпта, это вообще мощнейшая штука, если уметь готовить. Позволяет навешивать на классы, методы или свойства дополнительные бирочки с информацией, которую потом можно вытащить и посмотреть.

import 'reflect-metadata';

// Вот делаем простой декоратор, который будет метаданные цеплять
function Log(target: any, key: string) {
  const types = Reflect.getMetadata('design:type', target, key);
  const paramtypes = Reflect.getMetadata('design:paramtypes', target, key);
  console.log(`${key} type: ${types.name}, param types: ${paramtypes?.map(p => p.name)}`);
}

class MyService {
  @Log
  greet(name: string): string {
    return `Hello, ${name}`;
  }
}
// Главное, не забудь в `tsconfig.json` опцию `emitDecoratorMetadata` включить, а то нихуя не заработает.
// Тогда при запуске в консоль вывалится: greet type: Function, param types: String

Где это всё, блядь, применяется на практике?

  • Всякие модные фреймворки: NestJS, TypeORM, class-validator — они на этом просто живут. Внедрение зависимостей, описание моделей для базы, валидация — всё через декораторы и метаданные, овердохуища удобства.
  • Свои велосипеды: Захотел сделать валидатор — пожалуйста. Напишешь декоратор @MaxLength(10), навесишь на поле, и потом в рантайме проверишь, что пользователь не насрал туда строку в сто символов.

2. Метаданные пакетов (этот вездесущий package.json): Ну, тут всё просто, как три копейки. В Node.js package.json — это и есть главные метаданные твоего модуля. Я, например, ими пользуюсь вот для чего:

{
  "name": "my-service",
  "version": "1.0.0",
  "engines": { "node": ">=18.0.0" }, // Метаданные о том, на какой версии Node это говно должно работать
  "config": { "defaultPort": 3000 } // А это уже моя кастомная хрень для конфигурации
}

Достучаться до этого можно, например, через process.env.npm_package_config_defaultPort. Удобно, ёпта.

3. Метаданные в HTTP-запросах и ответах: А это вообще классика. Все эти заголовки — X-Request-Id, User-Agent — они же и есть чистейшие метаданные! Нужны для отслеживания сквозных запросов или логирования.

app.use((req, res, next) => {
  req.id = uuid.v4(); // Вот, прилепили к объекту запроса свою метку — это и есть метаданные
  res.setHeader('X-Response-Time', Date.now() - req.startTime); // И в ответ тоже можно воткнуть
  next();
});

Короче, суть в том, что метаданные — это не какая-то хитрая жопа, а просто дополнительная информация, которая помогает твоему коду быть умнее и знать о себе больше. Главное — не переборщить, а то можно так задекорировать, что сам потом разбираться будешь, как в джунглях.