В Superstruct примитивные структуры — это строительные блоки, на
которых формируется любая схема валидации. К ним относятся
string, number, boolean,
array, object, а также специализированные виды
вроде literal, enums, record.
Расширение базовых типов в контексте библиотеки означает не изменение
самих примитивов, а создание новых структур поверх них через композицию,
уточнение и трансформацию.
Ключевая идея заключается в том, что любой более сложный тип всегда
можно выразить через комбинацию базовых структур и функций модификации:
refine, coerce, defaulted,
optional, union,
intersection.
Механизм refine позволяет накладывать дополнительные
ограничения на уже существующий базовый тип без изменения его
структуры.
Пример логического расширения числового типа:
import { number, refine } from 'superstruct'
const PositiveNumber = refine(number, 'PositiveNumber', (value) => {
return value > 0
})
Здесь создаётся новый тип, основанный на number, но
ограниченный только положительными значениями.
Такой подход используется для:
Пример строкового расширения:
const NonEmptyString = refine(string, 'NonEmptyString', (value) => {
return value.trim().length > 0
})
Функция define позволяет описывать полностью новые
структуры с нуля, сохраняя интеграцию с системой валидации
Superstruct.
import { define } from 'superstruct'
const EvenNumber = define('EvenNumber', (value) => {
return typeof value === 'number' && value % 2 === 0
})
В отличие от refine, здесь нет обязательной привязки к
базовому типу, что даёт большую гибкость, но требует полной ручной
проверки.
Типичные сценарии:
Расширение базовых типов часто достигается через пересечение структур.
import { object, string, number, intersect } from 'superstruct'
const Named = object({
name: string,
})
const Aged = object({
age: number,
})
const Person = intersect([Named, Aged])
Результатом становится структура, которая объединяет требования обеих схем.
Особенности:
Если расширение базового типа подразумевает несколько допустимых
форм, применяется union.
import { union, string, number } from 'superstruct'
const StringOrNumber = union([string, number])
Такой подход используется для:
coerce позволяет расширять базовый тип за счёт
автоматического преобразования входных данных перед валидацией.
import { coerce, string, number } from 'superstruct'
const NumberFromString = coerce(number, string, (value) => {
return Number(value)
})
Это фактически расширение базового типа number
возможностью принимать строковое представление.
Типовые сценарии:
Расширение структуры часто включает автоматическое заполнение отсутствующих полей.
import { defaulted, number } from 'superstruct'
const WithDefault = defaulted(number, 10)
В объектных структурах:
import { object, defaulted, string } from 'superstruct'
const Config = object({
mode: defaulted(string, 'dev'),
})
Такой механизм расширяет базовый тип, добавляя поведение по умолчанию без изменения логики валидации.
Гибкость базовых структур достигается через ослабление обязательности полей.
import { object, string, optional } from 'superstruct'
const User = object({
name: string,
nickname: optional(string),
})
Или через частичное преобразование:
import { partial } from 'superstruct'
const PartialUser = partial(User)
Эти механизмы позволяют:
Базовый тип массива может быть расширен за счёт сложных структур элементов.
import { array, object, string, number } from 'superstruct'
const Users = array(
object({
name: string,
score: number,
})
)
Расширение здесь происходит на уровне элемента массива, а не контейнера.
Дополнительные варианты:
Одним из ключевых способов расширения базовых типов является композиция уже существующих структур.
const Id = refine(string, 'Id', (v) => v.length === 24)
const Entity = object({
id: Id,
})
Переиспользование позволяет:
Тип record позволяет расширять базовые структуры до
динамических словарей.
import { record, string, number } from 'superstruct'
const Scores = record(string, number)
Это фактически расширение примитивного словаря до типизированной структуры ключ-значение.
Применение:
Базовый string часто расширяется через дополнительные
проверки:
Пример через refine:
const Username = refine(string, 'Username', (value) => {
return /^[a-z0-9_]{3,16}$/.test(value)
})
Такое расширение формирует доменные типы поверх примитивов.
const Percentage = refine(number, 'Percentage', (value) => {
return value >= 0 && value <= 100
})
Также возможны комбинированные ограничения:
const EvenPositive = refine(number, 'EvenPositive', (v) => {
return v > 0 && v % 2 === 0
})
Расширение базовых типов часто строится как последовательность трансформаций:
const Base = number
const Positive = refine(Base, 'Positive', v => v > 0)
const EvenPositive = refine(Positive, 'EvenPositive', v => v % 2 === 0)
Такая цепочка позволяет формировать строгие доменные ограничения без повторного описания логики.
Иногда требуется генерировать структуры на лету:
const createMinMaxNumber = (min, max) =>
refine(number, 'MinMaxNumber', (v) => v >= min && v <= max)
Это расширяет базовый тип number параметризованной
логикой.
Используется для:
При расширении базовых типов важно учитывать читаемость ошибок. Superstruct позволяет задавать имена типов, что улучшает диагностику:
refine(number, 'Age', (v) => v >= 0)
Имя 'Age' становится частью диагностического сообщения,
что делает поведение расширенного типа более прозрачным в сложных
композициях.
Расширение базовых типов в Superstruct формируется как комбинация нескольких принципов:
refinedefineobject, array,
intersectunioncoerceoptional и partialВ результате базовые типы перестают быть фиксированными конструкциями и превращаются в систему, где каждый примитив служит точкой роста для более сложных доменных моделей.