Skip to content

lazy ​

Схема, которая выбирается в момент проверки. Два случая:

  • Рекурсия — данные содержат сами себя: категории с подкатегориями, комментарии с ответами, меню.
  • Схема по значению или соседним полям — например, у организации ИНН из 10 цифр и обязательный КПП, у физлица — 12 цифр и без КПП.

Сигнатура ​

ts
function lazy<T>(getter: (value: unknown, info: { parent: unknown; root: unknown }) => Schema<T>): Schema<T>

Параметры ​

ПараметрТипОписание
getter(value, { parent, root }) => Schema<T>Функция, которая возвращает схему. Вызывается при каждой проверке и получает значение, объект с соседними полями и все данные. Для рекурсии аргументы не нужны: () => Category

Особенности ​

  • TypeScript не может вывести рекурсивный тип сам, поэтому тип нужно объявить и указать у схемы: const Category: Schema<Category> = ....
  • Защита от глубоких данных. Если клиент пришлёт вложенность в десятки тысяч уровней, обычная рекурсия уронит процесс переполнением стека. lazy останавливается на глубине maxDepth (по умолчанию 256) и возвращает ошибку depth — сервер ответит 422, а не упадёт. Лимит меняется опцией maxDepth у validate.

Коды ошибок ​

КодКогдаСообщение по умолчанию
depthВложенность больше maxDepthToo deeply nested (max {max})

Примеры ​

Дерево категорий ​

ts
import { object, string, array, optional, lazy, type Schema } from 'tinyval';

type Category = { name: string; children?: Category[] };

const Category: Schema<Category> = object({
  name: string(),
  children: optional(array(lazy(() => Category))),
});

validate(Category, { name: 'root', children: [{ name: 'a', children: [{}] }] }).errors;
// { 'children.0.children.0.name': 'Required' }

Схема по соседнему полю ​

ts
const Company = object({
  type: oneOf(['person', 'org']),
  inn: lazy((_, { parent }) => pipe(string(), length(parent.type === 'org' ? 10 : 12), inn())),
  kpp: lazy((_, { parent }) => (parent.type === 'org' ? pipe(string(), pattern(/^\d{4}[\dA-Z]{2}\d{3}$/)) : optional(string()))),
});

validate(Company, { type: 'org', inn: '500100732259' }).errors;
// { inn: 'Must be exactly 10 characters long', kpp: 'Required' }

Схема по значению ​

ts
// id: a number or a UUID string
const id = lazy((value) => (typeof value === 'number' ? pipe(number(), integer()) : pipe(string(), uuid())));

Для типов TypeScript все ветки должны возвращать схему одного типа или типа-объединения: для вариантов с разной структурой удобнее variant или union.

Комментарии с ответами ​

ts
type Comment = { text: string; replies: Comment[] };

const Comment: Schema<Comment> = object({
  text: pipe(string(), trim(), minLength(1), maxLength(2000)),
  replies: optional(array(lazy(() => Comment)), () => []),
});

parse(Comment, body, { maxDepth: 10 }); // no more than 10 reply levels

См. также ​