Автоматический вывод типов

В библиотеке 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()string
  • number()number
  • boolean()boolean
  • literal() → конкретное литеральное значение
  • 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) и вывод альтернативных типов

При использовании 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, механизм вывода типов имеет ряд принципиальных ограничений:

  • отсутствует возможность уточнения пользовательских предикатов до узких литеральных типов;
  • runtime-логика не влияет на статическую типизацию;
  • сложные условные преобразования типов не поддерживаются напрямую;
  • преобразование значений (coerce) не изменяет статический тип результата.

Итоговая модель типизации

Система Superstruct опирается на единый принцип: структура данных является источником истины как для проверки значений, так и для статической типизации. Это исключает необходимость дублирования интерфейсов и обеспечивает согласованность между рантаймом и компиляцией TypeScript.