В библиотеке Superstruct ключевая особенность связана с тесной интеграцией рантайм-схем и статической типизации. Механизм автоматического вывода типов позволяет синхронизировать описание структуры данных с TypeScript-типами без ручного дублирования.
Каждая структура, создаваемая через примитивы Superstruct, содержит
информацию, достаточную для восстановления соответствующего
TypeScript-типа. Это достигается за счёт условных типов и утилиты
Infer.
import { object, string, number, Infer } from "superstruct";
const User = object({
id: number(),
name: string(),
});
type User = Infer<typeof User>;
В результате User автоматически становится:
type User = {
id: number;
name: string;
}
Фактически структура данных и её тип описываются в одном месте, что устраняет рассинхронизацию между валидацией и типизацией.
Каждый примитив Superstruct имеет строго определённое соответствие TypeScript:
string() → stringnumber() → numberboolean() → booleanliteral() → конкретное литеральное значениеenums() → объединение литераловПример:
import { string, number, boolean, Infer } from "superstruct";
const ID = number();
const Name = string();
const Active = boolean();
type ID = Infer<typeof ID>; // number
type Name = Infer<typeof Name>; // string
type Active = Infer<typeof Active>; // boolean
При использовании object происходит рекурсивный разбор
вложенных структур. Каждый ключ анализируется отдельно, после чего
итоговая типизация собирается в единый объект.
import { object, string, number, Infer } from "superstruct";
const Profile = object({
username: string(),
stats: object({
followers: number(),
following: number(),
}),
});
type Profile = Infer<typeof Profile>;
Результирующий тип:
type Profile = {
username: string;
stats: {
followers: number;
following: number;
};
}
Механизм вывода типов сохраняет вложенность без необходимости дополнительных аннотаций.
При использовании array выводится тип элемента, после
чего формируется массив этого типа.
import { array, string, Infer } from "superstruct";
const Tags = array(string());
type Tags = Infer<typeof Tags>; // string[]
Если элемент массива является сложной структурой, типизация сохраняет полную вложенность:
import { array, object, string, number, Infer } from "superstruct";
const Posts = array(
object({
id: number(),
title: string(),
})
);
type Posts = Infer<typeof Posts>;
Результат:
type Posts = {
id: number;
title: string;
}[];
При использовании union формируется объединение всех
возможных типов.
import { union, string, number, Infer } from "superstruct";
const Value = union([string(), number()]);
type Value = Infer<typeof Value>; // string | number
В более сложных случаях объединение сохраняет структуру каждого варианта:
import { union, object, string, number, Infer } from "superstruct";
const Response = union([
object({ status: string(), data: string() }),
object({ status: number(), error: string() }),
]);
type Response = Infer<typeof Response>;
Результат:
type Response =
| { status: string; dat a: string }
| { status: number; error: string };
При использовании optional тип автоматически расширяется
до объединения с undefined.
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;
}
Внутренне nickname интерпретируется как
string | undefined, но на уровне объекта отражается как
опциональное поле.
Литералы позволяют фиксировать конкретные значения, что напрямую отражается в типе.
import { literal, Infer } from "superstruct";
const Status = literal("success");
type Status = Infer<typeof Status>; // "success"
Комбинация литералов формирует строгие дискриминируемые типы:
import { union, literal, Infer } from "superstruct";
const Status = union([
literal("success"),
literal("error"),
]);
type Status = Infer<typeof Status>; // "success" | "error"
При создании пользовательских структур через define или
композицию других структур вывод типов сохраняется при условии
корректного описания базового типа.
import { define, Infer } from "superstruct";
const PositiveNumber = define("PositiveNumber", (value) => {
return typeof value === "number" && value > 0;
});
type PositiveNumber = Infer<typeof PositiveNumber>; // number
Несмотря на рантайм-проверку, TypeScript-тип остаётся
number, так как система не выполняет автоматическую
семантическую специализацию.
При комбинировании структур через intersection типы
объединяются по принципу пересечения.
import { object, string, number, intersection, 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,
array,
union,
string,
number,
Infer,
} from "superstruct";
const Schema = object({
users: array(
object({
id: number(),
role: union([string(), number()]),
})
),
});
type Schema = Infer<typeof Schema>;
Результат:
type Schema = {
users: {
id: number;
role: string | number;
}[];
}
Несмотря на высокую степень интеграции с TypeScript, механизм вывода типов имеет ряд принципиальных ограничений:
coerce) не изменяет
статический тип результата.Система Superstruct опирается на единый принцип: структура данных является источником истины как для проверки значений, так и для статической типизации. Это исключает необходимость дублирования интерфейсов и обеспечивает согласованность между рантаймом и компиляцией TypeScript.