Композиция схем в Superstruct строится вокруг идеи повторного использования валидаторов и их комбинирования в более сложные структуры без дублирования логики. Вместо попыток описывать каждую форму данных «с нуля» система опирается на небольшие, атомарные проверки, которые можно собирать как конструктор.
В основе Superstruct лежит функция создания структур, которая возвращает валидатор:
import { struct } from 'superstruct'
const User = struct({
id: 'number',
name: 'string',
})
Такие структуры уже являются композиционными единицами. Их можно переиспользовать, вкладывать друг в друга и расширять.
Ключевая особенность подхода — структура не является конечной сущностью, она остаётся строительным блоком.
Самый простой уровень композиции — вложенные объекты. Один struct используется внутри другого:
import { struct } from 'superstruct'
const Address = struct({
city: 'string',
zip: 'number',
})
const User = struct({
name: 'string',
address: Address,
})
Здесь Address становится частью более сложной схемы.
Такой подход позволяет:
Для композиции на уровне расширения объектов используется паттерн объединения структур.
В Superstruct это часто реализуется через ручное слияние или вспомогательные конструкции:
const BaseUser = struct({
id: 'number',
name: 'string',
})
const AdminUser = struct({
...BaseUser.schema,
role: 'string',
})
Хотя библиотека не навязывает строгое наследование, композиция через spread-структуру остаётся распространённым подходом.
Одним из мощных механизмов композиции является пересечение типов — когда данные должны соответствовать сразу нескольким схемам.
import { intersection, struct } from 'superstruct'
const HasId = struct({
id: 'number',
})
const HasTimestamps = struct({
createdAt: 'number',
updatedAt: 'number',
})
const Entity = intersection([HasId, HasTimestamps])
Такой подход полезен, когда:
Композиция не всегда означает объединение. Часто требуется выбор одного из вариантов.
import { union, struct } from 'superstruct'
const Cat = struct({
type: 'string',
meows: 'boolean',
})
const Dog = struct({
type: 'string',
barks: 'boolean',
})
const Pet = union([Cat, Dog])
Union используется для моделирования:
Валидация проходит успешно, если соответствует хотя бы одной схеме.
Опциональность позволяет постепенно усложнять структуру без разрушения базового контракта.
import { struct, optional } from 'superstruct'
const User = struct({
name: 'string',
age: optional('number'),
})
Композиционно это важно тем, что позволяет:
Композиция часто включает в себя не только проверку, но и нормализацию данных.
import { struct, defaulted } from 'superstruct'
const Settings = struct({
theme: defaulted('string', 'light'),
})
Такие схемы позволяют:
Композиция становится значительно мощнее, когда включается преобразование входных значений.
import { struct, coerce } from 'superstruct'
const NumberFromString = coerce('number', 'string', (value) =>
Number(value)
)
Это позволяет строить цепочки:
Такая модель особенно полезна при работе с внешними API и пользовательским вводом.
Схемы могут ссылаться сами на себя, что позволяет описывать древовидные структуры.
import { struct } from 'superstruct'
const Node = struct.lazy(() =>
struct({
value: 'string',
children: struct.array(Node),
})
)
Это фундамент для:
Композиция здесь становится бесконечно расширяемой.
Одним из практичных подходов является создание функций, возвращающих структурированные схемы.
const withMeta = (base) =>
struct({
...base.schema,
meta: 'object',
})
const User = withMeta(
struct({
name: 'string',
})
)
Так формируются «паттерны схем», которые можно применять к разным моделям.
Иногда требуется взять только часть структуры и встроить её в другую модель.
const Auth = struct({
email: 'string',
password: 'string',
})
const Login = struct({
email: Auth.schema.email,
password: Auth.schema.password,
})
Такой подход позволяет:
Помимо структуры данных, композиция может включать бизнес-правила.
import { struct, refine } from 'superstruct'
const PositiveNumber = refine('number', 'PositiveNumber', (value) => {
return value > 0
})
Refine позволяет накладывать дополнительные условия поверх базовых типов, создавая слоистую модель проверки.
В реальных приложениях схемы редко существуют изолированно. Они образуют слои:
string, number)struct)union, intersection)optional, defaulted)coerce)refine)Каждый слой добавляет новую семантику, не разрушая предыдущий.
Использование композиции схем позволяет выстраивать архитектуру данных, в которой:
В результате схемы перестают быть просто проверками и превращаются в формальное описание доменной модели приложения.