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 (структуры). Это означает, что любые изменения
валидационной схемы сразу отражаются в типизации без дополнительного
ручного обновления интерфейсов.
Superstruct оперирует двумя уровнями:
Infer выступает мостом между этими уровнями, обеспечивая
единое описание данных.
const Post = object({
title: string(),
views: number(),
});
type Post = Infer<typeof Post>;
Тип Post эквивалентен:
type Post = {
title: string;
views: number;
};
Для простых структур поведение 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;
}[];
};
Вложенность любой глубины поддерживается без дополнительных усилий.
Для структур с альтернативными вариантами Infer
корректно выводит объединённые типы.
import { union, string, number, Infer } from "superstruct";
const Id = union([string(), number()]);
type Id = Infer<typeof Id>;
Результат:
type Id = string | number;
Такой подход полезен при работе с API, где идентификаторы могут иметь разные форматы.
При использовании 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;
};
При использовании defaulted структура продолжает
выводить базовый тип без учёта runtime-значения по умолчанию.
import { defaulted, number, Infer } from "superstruct";
const Age = defaulted(number(), 18);
type Age = Infer<typeof Age>;
Тип:
type Age = number;
TypeScript не включает значение по умолчанию в тип, поскольку оно существует только на уровне выполнения.
Комбинация модификаторов влияет на итоговую типизацию через union.
import { nullable, optional, string, Infer } from "superstruct";
const S = nullable(optional(string()));
type S = Infer<typeof S>;
Результат:
type S = string | null | undefined;
Такая модель позволяет точно отражать возможные состояния данных.
При создании кастомных валидаторов 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>;
Здесь тип определяется разработчиком, так как автоматический вывод невозможен.
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 работает исключительно на уровне статического анализа TypeScript и не имеет доступа к runtime-исполнению.
Использование Infer формирует архитектурный паттерн, при котором:
Это делает структуры Superstruct центральным элементом системы описания данных, а Infer — механизмом их переноса в статическую систему типов TypeScript.