Record

В Superstruct структура record предназначена для описания объектов-словарей, где набор ключей заранее не фиксирован, но строго контролируются типы этих ключей и значений. Это один из ключевых инструментов для моделирования динамических коллекций данных, таких как конфигурации, справочники, кэши и маппинги.

record описывает объект, в котором:

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

В отличие от object-подобных схем, где структура фиксирована, record ориентирован на произвольное количество однотипных полей.

Основная идея:

  • ключи не перечисляются вручную;
  • задаётся правило для допустимых ключей;
  • задаётся правило для всех значений.

Базовый синтаксис record

В Superstruct record создаётся с помощью функции:

import { record, string, number } from 'superstruct'

const Schema = record(string(), number())

Здесь:

  • string() описывает допустимые ключи;
  • number() описывает значения.

Пример валидного объекта:

const data = {
  a: 1,
  b: 2,
  c: 3
}

Любой ключ является строкой, и каждое значение — числом.

Ограничение ключей через типы

Строковые ключи

Наиболее частый сценарий — использование строковых ключей:

const UserScores = record(string(), number())

Пример:

{
  alice: 10,
  bob: 15,
  charlie: 20
}

Такой вариант эквивалентен словарю или хеш-таблице.

Числовые ключи

Хотя в JavaScript ключи объектов всегда приводятся к строке, Superstruct позволяет описывать числовые ключи:

import { record, number, string } from 'superstruct'

const NumericKeys = record(number(), string())

Пример:

{
  1: "one",
  2: "two",
  3: "three"
}

Фактически ключи будут строками "1", "2", но проверка будет учитывать числовую семантику.

Ограничение ключей через предикаты и шаблоны

Одной из наиболее мощных возможностей является использование pattern или пользовательских структур для ключей.

Регулярные выражения для ключей

import { record, string, pattern } from 'superstruct'

const HexMap = record(
  pattern(/^[a-f0-9]{6}$/),
  string()
)

Пример:

{
  ff0000: "red",
  00ff00: "green",
  0000ff: "blue"
}

Здесь ключи обязаны быть шестнадцатеричными строками.

Ограничение по набору значений ключей

Для строгого контроля используется enums-подобная структура через пользовательскую проверку:

import { record, string, enums } from 'superstruct'

const RoleMap = record(
  enums(['admin', 'user', 'guest']),
  string()
)

Пример:

{
  admin: "Alice",
  user: "Bob",
  guest: "Eve"
}

Любой ключ вне списка приведёт к ошибке валидации.

Значения сложных структур

record не ограничивается примитивами в значениях. Можно использовать вложенные структуры.

import { record, object, string, number } from 'superstruct'

const UserMap = record(
  string(),
  object({
    id: number(),
    name: string()
  })
)

Пример данных:

{
  u1: { id: 1, name: "Alice" },
  u2: { id: 2, name: "Bob" }
}

Такой подход часто используется для хранения сущностей, индексированных по идентификатору.

Вложенные Record структуры

record может быть вложенным, формируя многомерные словари.

const Matrix = record(
  string(),
  record(string(), number())
)

Пример:

{
  row1: { col1: 1, col2: 2 },
  row2: { col1: 3, col2: 4 }
}

Такая структура используется для:

  • матриц;
  • таблиц;
  • сложных конфигураций;
  • пространственных данных.

Поведение при валидации

При проверке данных Superstruct выполняет два уровня контроля:

  1. Проверка ключей согласно первой структуре.
  2. Проверка значений согласно второй структуре.

Если хотя бы одно значение не соответствует схеме, весь объект считается невалидным.

Пример ошибки:

const Schema = record(string(), number())

const data = {
  a: 1,
  b: "invalid"
}

Ошибка возникает из-за значения "invalid", так как оно не является числом.

Работа с пустыми объектами

Пустой объект всегда валиден для record, если нет дополнительных ограничений:

{}

Это поведение важно учитывать при работе с опциональными словарями, кэшами или накопительными структурами.

Комбинирование с другими структурами

Record внутри struct

import { struct, record, string, number } from 'superstruct'

const AppConfig = struct({
  settings: record(string(), string()),
  metrics: record(string(), number())
})

Пример:

{
  settings: {
    theme: "dark",
    language: "ru"
  },
  metrics: {
    clicks: 100,
    views: 200
  }
}

Record как часть union-структур

import { union, record, string, number } from 'superstruct'

const Data = union([
  record(string(), number()),
  record(string(), string())
])

Такой подход позволяет описывать гибкие схемы, где структура значений зависит от контекста.

Практическое применение record

1. Конфигурационные объекты

const Config = record(string(), string())

Используется для хранения переменных окружения, настроек модулей, параметров приложения.

2. Кэширование данных

const Cache = record(string(), object({
  value: string(),
  timestamp: number()
}))

Позволяет хранить данные по ключу с метаинформацией.

3. Индексация сущностей

const Users = record(string(), object({
  id: number(),
  email: string()
}))

Подходит для in-memory баз данных.

4. Локализация

const Translations = record(
  string(),
  record(string(), string())
)

Пример:

{
  en: { hello: "Hello", bye: "Goodbye" },
  ru: { hello: "Привет", bye: "Пока" }
}

Особенности производительности

record работает эффективно даже с большими объектами, поскольку:

  • проверка ключей выполняется по одному правилу;
  • нет необходимости описывать каждое поле;
  • структура остаётся плоской с точки зрения схемы.

Однако глубоко вложенные record могут увеличивать стоимость валидации из-за рекурсивной проверки значений.

Типичные ошибки при использовании record

Несоответствие типов значений

const Schema = record(string(), number())

{
  a: "1" // строка вместо числа
}

Недопустимые ключи

const Schema = record(pattern(/^[a-z]+$/), number())

{
  "123abc": 10 // не проходит регулярное выражение
}

Смешение структур

const Schema = record(string(), number())

{
  a: 1,
  b: { value: 2 } // объект вместо числа
}

Использование record как альтернатива Map

record часто используется вместо Map, когда:

  • требуется сериализация в JSON;
  • данные должны быть статичными объектами;
  • важна простота структуры.

В отличие от Map, структура record:

  • легко сериализуется;
  • валидируется;
  • интегрируется с остальными схемами Superstruct.

Динамическая природа record

Главное свойство record — способность описывать неизвестное количество полей при строгом контроле их формы. Это делает его универсальным инструментом для любых “словарных” структур данных, где важна не фиксированная схема объекта, а единообразие элементов внутри него.