Infer для получения типов

Infer в Superstruct используется для извлечения TypeScript-типа из структурной схемы (struct), описанной с помощью валидаторов библиотеки. Этот механизм позволяет автоматически синхронизировать runtime-валидацию и статическую типизацию, устраняя дублирование описаний типов и снижая вероятность расхождений между кодом проверки и типами TypeScript.

В основе подхода лежит преобразование структуры, созданной через API Superstruct, в соответствующий TypeScript-тип. Для этого используется утилитарный тип Infer.

import { object, string, number, Infer } fr om "superstruct";

const User = object({
  id: number(),
  name: string(),
});

type User = Infer<typeof User>;

В данном примере User (тип) автоматически выводится из User (структуры). Это означает, что любые изменения валидационной схемы сразу отражаются в типизации без дополнительного ручного обновления интерфейсов.


Связь runtime-структур и статических типов

Superstruct оперирует двумя уровнями:

  • runtime-уровень — проверка значений во время выполнения
  • type-level — вывод типов через TypeScript

Infer выступает мостом между этими уровнями, обеспечивая единое описание данных.

const Post = object({
  title: string(),
  views: number(),
});

type Post = Infer<typeof Post>;

Тип Post эквивалентен:

type Post = {
  title: string;
  views: number;
};

Infer с примитивными структурами

Для простых структур поведение Infer напрямую отражает базовые типы Superstruct.

import { string, number, boolean, Infer } from "superstruct";

type A = Infer<typeof string>;  // string
type B = Infer<typeof number>;  // number
type C = Infer<typeof boolean>; // boolean

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


Работа с массивами

При использовании структур массивов Infer выводит тип элемента внутри массива.

import { array, string, Infer } from "superstruct";

const Tags = array(string());

type Tags = Infer<typeof Tags>;

Результат:

type Tags = string[];

Массивы могут содержать любые сложные структуры:

const Users = array(
  object({
    id: number(),
    email: string(),
  })
);

type Users = Infer<typeof Users>;

Итоговый тип:

type Users = {
  id: number;
  email: string;
}[];

Объекты и вложенные структуры

Одно из ключевых применений Infer — работа с вложенными объектами.

const Comment = object({
  text: string(),
  likes: number(),
});

const Post = object({
  title: string(),
  comments: array(Comment),
});

type Post = Infer<typeof Post>;

Выводимый тип:

type Post = {
  title: string;
  comments: {
    text: string;
    likes: number;
  }[];
};

Вложенность любой глубины поддерживается без дополнительных усилий.


Объединения (union)

Для структур с альтернативными вариантами Infer корректно выводит объединённые типы.

import { union, string, number, Infer } from "superstruct";

const Id = union([string(), number()]);

type Id = Infer<typeof Id>;

Результат:

type Id = string | number;

Такой подход полезен при работе с API, где идентификаторы могут иметь разные форматы.


Пересечения (intersection)

При использовании intersection типы объединяются в единый объект.

import { intersection, object, string, number, Infer } from "superstruct";

const A = object({
  a: string(),
});

const B = object({
  b: number(),
});

const C = intersection([A, B]);

type C = Infer<typeof C>;

Результат:

type C = {
  a: string;
  b: number;
};

Опциональные поля

Опциональные поля сохраняют свою семантику и корректно отражаются в типе.

import { object, string, optional, Infer } from "superstruct";

const User = object({
  name: string(),
  nickname: optional(string()),
});

type User = Infer<typeof User>;

Результат:

type User = {
  name: string;
  nickname?: string;
};

Default-значения и Infer

При использовании defaulted структура продолжает выводить базовый тип без учёта runtime-значения по умолчанию.

import { defaulted, number, Infer } from "superstruct";

const Age = defaulted(number(), 18);

type Age = Infer<typeof Age>;

Тип:

type Age = number;

TypeScript не включает значение по умолчанию в тип, поскольку оно существует только на уровне выполнения.


Nullable и optional комбинации

Комбинация модификаторов влияет на итоговую типизацию через union.

import { nullable, optional, string, Infer } from "superstruct";

const S = nullable(optional(string()));

type S = Infer<typeof S>;

Результат:

type S = string | null | undefined;

Такая модель позволяет точно отражать возможные состояния данных.


Пользовательские структуры и Infer

При создании кастомных валидаторов Infer требует явного указания выходного типа через generics.

import { Struct, Infer } from "superstruct";

const PositiveNumber = new Struct<number, null>(
  "PositiveNumber",
  (value) => typeof value === "number" && value > 0,
  (value) => value
);

type PositiveNumber = Infer<typeof PositiveNumber>;

Здесь тип определяется разработчиком, так как автоматический вывод невозможен.


Вложенные generics и повторное использование типов

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

const BaseEntity = object({
  id: number(),
});

const User = object({
  name: string(),
});

const UserEntity = intersection([BaseEntity, User]);

type UserEntity = Infer<typeof UserEntity>;

Результат:

type UserEntity = {
  id: number;
  name: string;
};

Практика масштабирования схем

При росте проекта Infer становится центральным механизмом синхронизации типов. Схемы начинают выступать единым источником истины, а TypeScript-типизация полностью выводится из них.

const Pagination = object({
  page: number(),
  lim it: number(),
});

const Response = object({
  data: array(User),
  pagination: Pagination,
});

type Response = Infer<typeof Response>;

Тип автоматически отражает структуру API-ответа без дополнительных описаний.


Ограничения Infer

Несмотря на гибкость, существуют ограничения:

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

Эти ограничения связаны с тем, что Infer работает исключительно на уровне статического анализа TypeScript и не имеет доступа к runtime-исполнению.


Роль Infer в архитектуре типобезопасных приложений

Использование Infer формирует архитектурный паттерн, при котором:

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

Это делает структуры Superstruct центральным элементом системы описания данных, а Infer — механизмом их переноса в статическую систему типов TypeScript.