Типизация в Superstruct строится вокруг идеи строгого описания данных через композицию примитивов и структур. В отличие от рантайм-валидации без контекста типов, здесь каждая схема может быть связана с TypeScript-типом, что позволяет синхронизировать проверку данных и систему типов.
Ключевая особенность подхода — двойственная природа структур: они одновременно являются и валидаторами, и источником типов.
Каждый примитив Superstruct соответствует конкретному TypeScript-типу:
string() → stringnumber() → numberboolean() → booleanliteral(value) → точечный литералarray(struct) → массив соответствующего типаobject({...}) → объект с типизированными полямиПример базовой структуры:
import { struct } from "superstruct"
const User = struct({
id: "number",
name: "string",
active: "boolean"
})
TypeScript-интерпретация такой схемы:
type User = {
id: number
name: string
active: boolean
}
Таким образом, структура фактически описывает контракт данных.
InferОдним из ключевых механизмов является автоматическое извлечение типа структуры.
import { Infer } from "superstruct"
type User = Infer<typeof User>
Это позволяет поддерживать единый источник истины: схема становится одновременно валидатором и типовым описанием.
Особенно важно, что при изменении структуры тип обновляется автоматически, исключая рассинхронизацию между рантаймом и компиляцией.
Литеральные значения позволяют создавать строго ограниченные типы.
const Role = struct.union([
struct.literal("admin"),
struct.literal("user"),
struct.literal("guest")
])
TypeScript-эквивалент:
type Role = "admin" | "user" | "guest"
Это даёт возможность строить безопасные дискретные множества значений без ручного перечисления типов.
Union-типы позволяют описывать данные, которые могут соответствовать нескольким схемам одновременно.
const Id = struct.union([
"string",
"number"
])
Это соответствует:
type Id = string | number
При валидации Superstruct проверяет каждую альтернативу последовательно до первого успешного совпадения.
Пересечения позволяют объединять несколько структур в одну комплексную модель.
const Timestamped = struct({
createdAt: "number"
})
const Named = struct({
name: "string"
})
const Entity = struct.intersection([
Timestamped,
Named
])
Результат:
type Entity = {
createdAt: number
name: string
}
Пересечения особенно полезны при композиции доменных моделей.
Опциональность реализуется через обёртку optional.
const Profile = struct({
bio: struct.optional("string"),
avatar: struct.optional("string")
})
TypeScript:
type Profile = {
bio?: string
avatar?: string
}
Важно, что optional влияет не только на тип, но и на
поведение валидации — отсутствие поля не считается ошибкой.
Схемы могут задавать дефолтные значения, расширяя типизацию.
const Settings = struct({
theme: struct.defaulted("string", "light")
})
Тип:
type Settings = {
theme: string
}
Хотя тип остаётся string, семантика схемы уточняет, что
значение всегда будет присутствовать после обработки.
Типизация глубоко распространяется на вложенные объекты.
const Post = struct({
title: "string",
author: struct({
id: "number",
name: "string"
})
})
Результат:
type Post = {
title: string
author: {
id: number
name: string
}
}
Вложенность не ограничена уровнем глубины, что позволяет описывать сложные доменные модели без потери строгой типизации.
Массивы типизируются через вложенную структуру элемента.
const Tags = struct.array("string")
type Tags = string[]
Для сложных объектов:
const Comments = struct.array(
struct({
id: "number",
text: "string"
})
)
type Comments = {
id: number
text: string
}[]
Partial-структуры позволяют делать все поля необязательными.
const PatchUser = struct.partial({
name: "string",
age: "number"
})
type PatchUser = {
name?: string
age?: number
}
Это особенно важно для PATCH-запросов и частичных обновлений состояния.
Типизация в Superstruct опирается на композиционный подход. Схемы можно расширять без дублирования.
const BaseUser = struct({
id: "number"
})
const ExtendedUser = struct({
...BaseUser.schema,
email: "string"
})
Это приводит к типу:
type ExtendedUser = {
id: number
email: string
}
Композиция позволяет строить иерархии моделей без потери статической строгости.
Superstruct поддерживает создание кастомных валидаторов, которые также могут быть типизированы.
import { struct, define } from "superstruct"
const PositiveNumber = define("PositiveNumber", (value) => {
return typeof value === "number" && value > 0
})
TypeScript:
type PositiveNumber = number
Хотя тип остаётся базовым, семантика уточняется на уровне валидации.
Некоторые структуры поддерживают приведение типов, влияющее на итоговую форму данных.
const NumberFromString = struct.coerce("number", "string", (value) =>
Number(value)
)
Это позволяет согласовать внешние данные (например, JSON API) с внутренней типизацией.
Для сложных моделей часто используется паттерн discriminated unions.
const Success = struct({
status: struct.literal("success"),
data: "string"
})
const Failure = struct({
status: struct.literal("error"),
message: "string"
})
const Response = struct.union([Success, Failure])
TypeScript:
type Response =
| { status: "success"; dat a: string }
| { status: "error"; message: string }
Ключевое поле status служит дискриминатором.
Superstruct позволяет строить строгие доменные модели, где каждый уровень данных описан схемой. Это создаёт систему, в которой:
Такой подход минимизирует расхождения между логикой приложения и фактическими данными.
Несмотря на мощную систему, типизация Superstruct имеет ограничения:
Тем не менее, базовый уровень строгости остаётся высоким и покрывает большинство прикладных сценариев.
В типичных приложениях Superstruct используется как слой между внешними данными и внутренними моделями. Типизация играет роль гаранта согласованности:
Это позволяет выстраивать предсказуемую архитектуру данных без дублирования описаний типов.