lazy
A schema chosen at validation time. Two cases:
- Recursion — data that contains itself: categories with subcategories, comments with replies, menus.
- A schema based on the value or sibling fields — e.g. a company has a 10-digit tax ID and a required KPP, an individual has 12 digits and no KPP.
Signature
ts
function lazy<T>(getter: (value: unknown, info: { parent: unknown; root: unknown }) => Schema<T>): Schema<T>Parameters
| Parameter | Type | Description |
|---|---|---|
getter | (value, { parent, root }) => Schema<T> | A function returning the schema. Called on every validation with the value, the object with sibling fields and all data. Recursion needs no arguments: () => Category |
Notes
- TypeScript can't infer a recursive type by itself, so declare the type and annotate the schema:
const Category: Schema<Category> = .... - Protection from deep data. If a client sends tens of thousands of nesting levels, plain recursion crashes the process with a stack overflow.
lazystops atmaxDepth(256 by default) and returns adeptherror — the server answers 422 instead of crashing. Change the limit with themaxDepthoption ofvalidate.
Error codes
| Code | When | Default message |
|---|---|---|
depth | Nesting is deeper than maxDepth | Too deeply nested (max {max}) |
Examples
Category tree
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' }Schema based on a sibling field
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' }Schema based on the value
ts
// id: a number or a UUID string
const id = lazy((value) => (typeof value === 'number' ? pipe(number(), integer()) : pipe(string(), uuid())));For TypeScript types all branches should return a schema of the same type or a union type: for variants with different structure variant or union are handier.
Comments with replies
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