Архитектура Superstruct построена вокруг идеи композиции примитивных проверок и создания новых типов через комбинацию уже существующих. Базовый набор структур покрывает стандартные сценарии (строки, числа, массивы, объекты), однако реальная прикладная разработка требует расширения системы собственными правилами валидации и преобразования данных.
Механизм расширений в Superstruct не является отдельным API уровня «плагинов» — он строится через композицию функций и обёрток вокруг базовых структур. Это делает систему предсказуемой и легко интегрируемой в типичные JavaScript-проекты без дополнительных зависимостей.
Расширение структуры в Superstruct базируется на трёх ключевых идеях:
Любое расширение можно свести к следующей модели:
refineОдним из ключевых инструментов является refine,
позволяющий добавлять пользовательскую проверку к уже существующей
структуре.
import { string, refine } from "superstruct";
const NonEmptyString = refine(string(), "NonEmptyString", (value) => {
return value.length > 0;
});
В данном примере создаётся новая структура, основанная на строке, но с дополнительным ограничением: строка не может быть пустой.
refine принимает три параметра:
Функция-предикат должна возвращать true или
false. При возврате false структура считается
невалидной.
coerceВо многих случаях требуется не только проверка, но и преобразование
входных данных. Для этого используется coerce.
import { string, coerce } from "superstruct";
const NumberFromString = coerce(string(), (value) => {
if (typeof value === "number") return String(value);
return value;
});
Однако более типичный сценарий — преобразование строки в число:
import { number, string, coerce } from "superstruct";
const NumberFromString = coerce(number(), string(), (value) => {
return Number(value);
});
coerce работает в два этапа:
Это делает возможным создание «умных» структур, которые автоматически адаптируют входные данные.
Расширения в Superstruct часто оформляются как функции, возвращающие структуры. Это позволяет параметризовать поведение.
import { string, refine } from "superstruct";
const maxLength = (n) =>
refine(string(), `MaxLength(${n})`, (value) => {
return value.length <= n;
});
const ShortString = maxLength(10);
Здесь maxLength — фабрика структур, создающая новые
правила на лету.
Расширения становятся наиболее мощными при композиции нескольких уровней логики.
import { string, pattern, refine } from "superstruct";
const Alphanumeric = pattern(string(), /^[a-z0-9]+$/i);
const Username = refine(Alphanumeric, "Username", (value) => {
return value.length >= 3 && value.length <= 16;
});
Здесь используются сразу три уровня:
Объектные структуры позволяют строить сложные доменные модели и расширять их без дублирования логики.
import { object, string, number, refine } from "superstruct";
const User = object({
name: string(),
age: number(),
});
const AdultUser = refine(User, "AdultUser", (value) => {
return value.age >= 18;
});
Подобный подход позволяет отделять структурную валидацию от бизнес-логики.
Расширения часто организуются слоями:
Пример:
import { string, pattern, refine } from "superstruct";
const BaseEmail = string();
const EmailFormat = pattern(BaseEmail, /^[^\s@]+@[^\s@]+\.[^\s@]+$/);
const CorporateEmail = refine(EmailFormat, "CorporateEmail", (value) => {
return value.endsWith("@company.com");
});
Такой подход делает структуру легко расширяемой без изменения предыдущих слоёв.
Superstruct позволяет строить иерархии расширений, где каждая новая структура базируется на предыдущей.
import { number, refine } from "superstruct";
const Positive = refine(number(), "Positive", (v) => v > 0);
const PositiveInteger = refine(Positive, "PositiveInteger", Number.isInteger);
Каждый уровень добавляет новое ограничение, не нарушая предыдущие.
Фабрики позволяют стандартизировать создание расширений в проекте.
import { refine, string } from "superstruct";
const createEnumString = (values) =>
refine(string(), "EnumString", (value) => {
return values.includes(value);
});
const Status = createEnumString(["pending", "active", "disabled"]);
Такой подход особенно полезен в крупных проектах, где множество однотипных структур.
Хотя Superstruct не имеет встроенного контекста выполнения, его можно эмулировать через замыкания.
import { refine, string } from "superstruct";
const uniqueInSet = (set) =>
refine(string(), "UniqueInSet", (value) => {
if (set.has(value)) return false;
set.add(value);
return true;
});
Здесь структура зависит от внешнего состояния, что позволяет реализовывать проверки уникальности.
При проектировании пользовательских структур часто возникают типовые проблемы:
Логика проверки и трансформации должна быть разделена через
coerce, иначе структура становится непредсказуемой.
Чрезмерное количество refine ухудшает читаемость и
отладку.
Каждое расширение должно сохранять смысл базового типа, иначе композиция становится нелогичной.
Расширения Superstruct наиболее эффективно работают в модели чистой композиции:
const FinalType =
refine(
refine(
pattern(string(), /^[a-z]+$/i),
"LengthCheck",
(v) => v.length > 3
),
"BusinessRule",
(v) => v !== "admin"
);
Хотя такая запись выглядит многослойной, она сохраняет прозрачность каждого правила.
Система расширений превращает Superstruct в инструмент не только валидации, но и моделирования доменной логики. За счёт композиции можно:
Расширения становятся промежуточным слоем между сырыми данными и прикладной логикой, формируя устойчивую структуру обработки данных в приложении.