npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@astral/validations

v4.20.0

Published

Библиотека для валидаций в функциональном стиле.

Downloads

725

Readme

@astral/validations

Библиотека для валидаций в функциональном стиле.

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

  • ⚡️️️️ Ориентирована на специфику frontend-приложений
  • ⚡️️️️ Функциональный стиль
  • ⚡️️️️ Валидация по схеме, а также использование правил валидации вне контекста схемы
  • ⚡️️️️ Оптимизирована для валидации форм (есть поддержка react-hook-form)
  • ⚡️️️️ Полноценная поддержка tree shaking
  • ⚡️️️️ Простота создания кастомных правил валидации

Table of contents


Installation

npm i --save @astral/validations
yarn add @astral/validations

Basic usage

Валидация объекта с вложенным массивом.

Codesandbox

import {
  object,
  array,
  arrayItem,
  string,
  optional,
  min,
  number,
  toPrettyError,
} from '@astral/validations';

type Permission = {
  id: number;
  description: string;
};

type User = {
  name: string;
  surname?: string;
  info: {
    permissions: Permission[];
  };
};

const validate = object<User>({
  name: string(),
  surname: optional(string()),
  info: object<User['info']>({
    permissions: array(
      arrayItem(
        object<Permission>({
          id: number(),
          description: string(min(2)),
        }),
      ),
    ),
  }),
});

// undefined
validate({
  name: 'Vasya',
  info: {
    permissions: [{ id: 1, description: 'my permission' }],
  },
});

// { info: { permissions: [{ description: 'Обязательно' }] } }
toPrettyError(
  validate({
    name: 'Vasya',
    info: {
        permissions: [{ id: 1 }],
    },
  })
);

Валидация отдельных value

import { number, string, max } from '@astral/validations';

// undefined
number()(22)

// { message: 'Обязательно' }
string()('')

// { message: 'Макс. символов: 5' }
string(max(5))('123456')

Guards

Guard - правило, выполняющее проверку на тип данных. Guard должен быть предикатом для любой валидации.

Каждый guard:

  • Проверяет значение на required (для каждого типа данных своя проверка)
  • Имеет метод define, позволяющий переопределять стандартные параметры guard

number

  • Возвращает ошибку если:
    • Тип value не number
    • Value является NaN
    • Value является Infinity
  • Проверяет value на required
  • Выполняет композицию правил, переданных в параметры
import { number, min, max } from '@astral/validations';

const validate = number(min(1), max(22));

// undefined
validate(20)

// { message: 'Не число' }
validate('string')

// { message: 'Некорректное число' }
validate(NaN)

// { message: 'Бесконечное число' }
validate(Infinity)

// { message: 'Обязательно' }
validate(undefined)

// { message: 'Обязательно' }
validate(null)

min number

Позволяет указать ограничение на минимальное число.

import { number, min } from '@astral/validations';

const validate = number(min(1));

// undefined
validate(20)

// undefined
validate(1)

// { message: 'Не меньше: 1' }
validate(0)

max number

Позволяет указать ограничение на максимальное число.

import { number, max } from '@astral/validations';

const validate = number(max(4));

// undefined
validate(4)

// undefined
validate(1)

// { message: 'Не больше: 4' }
validate(10)

integer

Проверяет является ли значение целым числом.

import { number, integer } from '@astral/validations';

const validate = number(integer(5));

// undefined
validate(5)

// undefined
validate(7)

// { message: 'Только целые числа' }
validate(3.14)

positiveNumber

Проверяет является ли значение положительным числом.

import { number, positiveNumber } from '@astral/validations';

const validate = number(positiveNumber(3));

// undefined
validate(3)

// { message: 'Только положительное числа' }
validate(0)

// { message: 'Только положительное числа' }
validate(-1)

string

  • Возвращает ошибку если:
    • Тип value не string
  • Проверяет value на required
  • Выполняет композицию правил, переданных в параметры
import { string, min, onlyNumber } from '@astral/validations';

const validate = string(min(1), onlyNumber());

// undefined
validate('name')

// { message: 'Не является строкой' }
validate(20)

// { message: 'Обязательно' }
validate('')
// { message: 'Обязательно' }
validate(undefined)
// { message: 'Обязательно' }
validate(null)

min string

Позволяет указать ограничение на минимальное количество символов в строке.

import { string, min } from '@astral/validations';

const validate = string(min(2));

// undefined
validate('vasya')

// undefined
validate('va')

// { message: 'Мин. символов: 2' }
validate('v')

max string

Позволяет указать ограничение на максимальное количество символов в строке.

import { string, max } from '@astral/validations';

const validate = string(max(6));

// undefined
validate('hello')

// undefined
validate('va')

// { message: 'Макс. символов: 6' }
validate('long string')

email

Проверяет валиден ли email. Работает с русскими доменами и punycode

import { string, email } from '@astral/validations';

const validate = string(email());

// undefined
validate('[email protected]');


// { message: 'Некорректный E-mail' }
validate('example.ru');


//Пользовательское сообщение для ошибки с максимальным количеством символов
const validateEmail = email({ invalidLengthMessage: 'слишком длинный email' });

// { message: 'слишком длинный email' }
validateEmail('longlonglong.......')

//Проверка email с разрешенной кирилицей и punycode
const validateCyrillicEmail = email({ allowCyrillic: true });

// undefined
validateCyrillicEmail('test@домен.рф')

// undefined
validateCyrillicEmail('[email protected]')

guid

Проверяет валиден ли GUID.

import { string, guid } from '@astral/validations';

const validate = string(guid());

// undefined
validate('C56A4180-65AA-42EC-A945-5FD21DEC0538');


// { message: 'Некорректный GUID' }
validate('x56a4180-h5aa-42ec-a945-5fd21dec0538');

length

Проверяет значение на соответствие длине.

import { string, length } from '@astral/validations';

const validate = string(length(5));

// undefined
validate('aaaaa');


// { message: 'Кол-во символов должно быть: 5' }
validate('abc');

pattern

Проверяет строку на соответствие регулярному выражению.

import { string, pattern } from '@astral/validations';

const validate = string(
  pattern(/word/g, { message: 'Должен быть word' })
);

// undefined
validate('word')

// { message: 'Должен быть word' }
validate('vasya')

onlyNumber

Проверяет на наличие только чисел в строке

import { string, onlyNumber } from '@astral/validations';

const validate = string(onlyNumber());

// undefined
validate('12345')

// { message: 'Строка должна содержать только числа' }
validate('a12345')
validate('1.2345')
validate('-1.2345')

containsNumbers

Проверяет на наличие чисел в строке

import { string, containsNumbers } from '@astral/validations';

const validate = string(containsNumbers());

// undefined
validate('test12345')

// { message: 'Строка должна содержать числа' }
validate('test')

containsDifferentCases

Проверяет на наличие в строке символов разных регистров

import { string, containsDifferentCases } from '@astral/validations';

const validate = string(containsDifferentCases());

// undefined
validate('testTEST')
validate('тестТЕСТ')

// { message: 'Строка должна содержать символы разного регистра' }
validate('test')
validate('ТЕСТ')

containsPunctuationMarks

Проверяет на наличие в строке знаков пунктуации !$%&’()+,-./:;<=>?@[]^_{|}”

import { string, containsPunctuationMarks } from '@astral/validations';

const validate = string(containsPunctuationMarks());

// undefined
validate('test?')

// { message: 'Строка должна содержать знаки пунктуации' }
validate('test')

snils

Проверяет валиден ли СНИЛС

import { string, snils } from '@astral/validations';

const validate = string(snils());

// undefined
validate('15657325992')

// { message: 'Некорректный СНИЛС' }
validate('95145370511')
validate('156-573-259 92')

:information_source: Поддерживает exclude


textField

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

import { string, textField } from '@astral/validations';

const validate = string(textField());

// undefined
validate('!@#$%^&*()-_=+|[]{};:",.<>/?')
validate('абвАБВ abcABC')

// { message: 'Содержит запрещённые символы' }
validate('😀')
validate('∑')
validate('٩(◕‿◕。)۶')

mobilePhone

  • Проверяет валиден ли мобильный телефон
  • Валидный телефон начинается с "79" и не содержит символов, кроме цифр.
import { string, mobilePhone } from '@astral/validations';

const validate = string(mobilePhone());

// undefined
validate('79999999999')

// { message: 'Некорректный номер телефона' }
validate('7 (999) 99-99-999')
validate('89999999999')
validate('+79999999999')

:information_source: Поддерживает exclude


innUL

Проверяет валиден ли ИНН ЮЛ

import { string, innUL } from '@astral/validations';

const validate = string(innUL());

// undefined
validate('7728168971')

// { message: 'Некорректный ИНН ЮЛ' }
validate('0000000000')
validate('384212952720')
validate('7728168911')

:information_source: Поддерживает exclude


innIP

Проверяет валиден ли ИНН ИП

import { string, innIP } from '@astral/validations';

const validate = string(innIP());

// undefined
validate('384212952720')

// { message: 'Некорректный ИНН ИП' }
validate('3842129527')
validate('384212952a20')
validate('+384212952720')

:information_source: Поддерживает exclude


innFL

Проверяет валиден ли ИНН ФЛ

import { string, innFL } from '@astral/validations';

const validate = string(innFL());

// undefined
validate('384212952720')
validate('000000000000')

// { message: 'Некорректный ИНН ФЛ' }
validate('3842129527')
validate('384212952a20')
validate('+384212952720')

:information_source: Поддерживает exclude


innTwelveSymbols

Проверяет валиден ли ИНН из 12 символов

import { string, innTwelveSymbols } from '@astral/validations';

const validate = string(innTwelveSymbols());

// undefined
validate('384212952720')

// { message: 'Некорректный ИНН' }
validate('3842129527')
validate('384212952a20')
validate('+384212952720')

:information_source: Поддерживает exclude


kpp

Проверяет валиден ли КПП

import { string, kpp } from '@astral/validations';

const validate = string(kpp());

// undefined
validate('770201001');

// { message: 'Некорректный КПП' }
validate('123123')
validate('00000000')

:information_source: Поддерживает exclude


ogrnIP

Проверяет валиден ли ОГРН ИП

import { string, ogrnIP } from '@astral/validations';

const validate = string(ogrnIP());

// undefined
validate('8104338364837')

// { message: 'Некорректный ОГРН ИП' }
validate('1175958036814')
validate('1175958000004')
validate('1-22-33-44-5555555-6')

:information_source: Поддерживает exclude


ogrnUL

Проверяет валиден ли ОГРН ЮЛ

import { string, ogrnUL } from '@astral/validations';

const validate = string(ogrnUL());

// undefined
validate('1214000000092')

// { message: 'Некорректный ОГРН ЮЛ' }
validate('1175958036814')
validate('1175958000004')
validate('1-22-33-5555555-6')

:information_source: Поддерживает exclude


personName

Проверяет валидно ли имя

Требования на реализацию

import { string, personName } from '@astral/validations';

const validate = string(personName());

// undefined
validate('Иван');
validate('иван');

// { message: 'Проверьте имя' }
validate('');
validate('Иван--Иван');

personSurname

Проверяет валидно ли фамилия

Требования на реализацию

import { string, personSurname } from '@astral/validations';

const validate = string(personSurname());

// undefined
validate('Иванов');
validate('иванов');

// { message: 'Проверьте фамилию' }
validate('');
validate('Иванов--иванов');

personPatronymic

Проверяет валидно ли отчество

Требования на реализацию

import { string, personPatronymic } from '@astral/validations';

const validate = string(personPatronymic());

// undefined
validate('Иванович');
validate('иванович');


// { message: 'Проверьте отчество' }
validate('');
validate('Иванович--Иванович');

passportSeries

Проверяет валидна ли серия паспорта

Требования на реализацию

import { string, passportSeries } from '@astral/validations';

const validate = string(passportSeries());

// undefined
validate('9217');

// { message: 'Проверьте серию' }
validate('0017');

// { message: 'Длина поля должна быть равна 4 символам' }
validate('917');

// { message: 'Только цифры' }
validate('91а7');

passportNumber

Проверяет валиден ли номер паспорта

Требования на реализацию

import { string, passportNumber } from '@astral/validations';

const validate = string(passportNumber());

// undefined
validate('704564');

// { message: 'Проверьте номер' }
validate('000100');

// { message: 'Длина поля должна быть равна 6 символам' }
validate('7045');

// { message: 'Только цифры' }
validate('70а5');

passportCode

Проверяет валиден ли код паспорта

Требования на реализацию

import { string, passportCode } from '@astral/validations';

const validate = string(passportCode());

// undefined
validate('123256');

// { message: 'Проверьте код' }
validate('000-456');

// { message: 'Длина поля должна быть равна 6 символам' }
validate('1234');

// { message: 'Только цифры' }
validate('1а3');

date

  • Возвращает ошибку если:
    • Тип value не является объектом Date
    • Date является invalid date
  • Проверяет value на required
  • Выполняет композицию правил, переданных в параметры
import { date } from '@astral/validations';

const validate = date();

// undefined
validate(new Date());

// { message: 'Некорректная дата' }
validate(new Date('22.22.2022'));

// { message: 'Не дата' }
validate('12.12.2022');

// { message: 'Обязательно' }
validate(undefined);

min date

Позволяет указать минимальную дату. При сверке дат игнорируется время, которое может быть отличное от 00:00:00 в объекте Date.

import { date, min } from '@astral/validations';

const validate = date(
  min(new Date('12-12-2022'), { message: 'Начиная с 12 января 2022 года' }),
);

// { message: 'Начиная с 12 января 2022 года' }
validate(new Date('12-11-2022'));

// undefined
validate(new Date('12-14-2022'));

max date

Позволяет указать максимальную дату. При сверке дат игнорируется время, которое может быть отличное от 00:00:00 в объекте Date.

import { date, max } from '@astral/validations';

const validate = date(
  max(new Date('12-12-2022'), { message: 'Не позднее 12 января 2022 года' }),
);

// { message: 'Не позднее 12 января 2022 года' }
validate(new Date('15-11-2022'));

// undefined
validate(new Date('02-01-2021'));

#min years old date

Принимает возраст и вычитает переданное количество лет из текущей даты. Позволяет кастомизировать текст ошибки.

import { date, minYearsOld } from '@astral/validations';

const validate = date(
  minYearsOld(18, {
    customErrorMessage:
            'Только совершеннолетние могут воспользоваться данной услугой',
  }),
);

// { message: 'Только совершеннолетние могут воспользоваться данной услугой' }
validate(new Date('15.11.2022'));

// undefined
validate(new Date('10.10.2005'));

rangeDate

  • Проверяет даты интервала на обязательность заполнения, валидность значений и хронологический порядок
  • Выполняет композицию правил, переданных в параметры
import { rangeDate } from '@astral/validations';

const validate = rangeDate();

// { message: 'Укажите период' }
validate({});

// { message: 'Укажите дату окончания' }
validate({
  start: new Date('2024.09.05'),
})

// { message: 'Укажите дату начала' }
validate({
  end: new Date('2024.09.24'),
})

// { message: 'Дата начала некорректная' }
validate({
  start: new Date('2024.99.99'),
  end: new Date('2024.09.24'),
})

// { message: 'Дата окончания некорректная' }
validate({
  start: new Date('2024.09.05'),
  end: new Date('2024.99.99'),
})

// { message: 'Дата окончания не может быть раньше даты начала' }
validate({
  start: new Date('2024.09.24'),
  end: new Date('2024.09.05'),
})

// undefined
validate({
  start: new Date('2024.09.05'),
  end: new Date('2024.09.24'),
});


const customValidate = (...rules: DateRangeRule[]) => dateRange(...rules).define({
  required: { start: false, end: false },
  message: {
    startRequired: 'Укажите начало периода'
  }
})

// undefined
customValidate({
  start: new Date('2024.09.05'),
});

// { message: 'Укажите начало периода' }
validate({
  end: new Date('2024.09.05'),
})

rangeDateInterval

Позволяет ограничить интервал на конкретное значение. По умолчанию значение интервала задаётся в днях, но с помощью опции unit можно задать значение в месяцах или годах.

import { rangeDateInterval } from '@astral/validations';

const validate = rangeDate(rangeDateInterval({ limit: 14 }));

// { message: 'Период не может превышать 14 дней' }
validate({
  start: new Date('2024.09.05'),
  end: new Date('2024.09.24'),
});

// undefined
validate({
  start: new Date('2024.09.05'),
  end: new Date('2024.09.12'),
});

const validateMonthInterval = rangeDate(rangeDateInterval({ limit: 2, unit: 'month' }));

// { message: 'Период не может превышать 2 месяца' }
validateMonthInterval({
  start: new Date('2024.09.05'),
  end: new Date('2024.12.24'),
});

// undefined
validateMonthInterval({
  start: new Date('2024.09.05'),
  end: new Date('2024.09.12'),
});


rangeDateMinMax

Проверяет даты на минимальное и максимальное допустимое значение

import { rangeDateMinMax } from '@astral/validations';

const validate = rangeDate(rangeDateMinMax({ start: { min: { limit: new Date('2024.09.05') }}, end: { max: { limit: new Date('2024.09.15') } } }))

// { message: 'Дата начала должна быть позже 05.09.2024' }
validate({
  start: new Date('2024.09.01'),
  end: new Date('2024.09.10'),
});

// { message: 'Дата окончания должна быть раньше 15.09.2024}' }
validate({
  start: new Date('2024.09.06'),
  end: new Date('2024.09.20'),
});

// undefined
validate({
  start: new Date('2024.09.06'),
  end: new Date('2024.09.14'),
});

rangeDateNotEqual

Проверяет даты интервала на совпадение даты начала и окончания

import { rangeDateNotEqual } from '@astral/validations';

const validate = rangeDate(
  rangeDateNotEqual(),
);

// { message: 'Даты начала и окончания не могут совпадать' }
validate({
  start: new Date('2024.09.05'),
  end: new Date('2024.09.05'),
});

// undefined
validate({
  start: new Date('2024.09.05'),
  end: new Date('2024.09.06'),
});

boolean

  • Возвращает ошибку если:
    • Тип value не boolean
  • Проверяет value на required
  • Выполняет композицию правил, переданных в параметры
import { boolean } from '@astral/validations';

const validate = boolean();

// undefined
validate(true)

// { message: 'Не boolean' }
validate('string')

// { message: 'Обязательно' }
validate(false)

// { message: 'Обязательно' }
validate(undefined)

// { message: 'Обязательно' }
validate(null)

object

  • Позволяет валидировать объект по схеме
  • Возвращает ошибку если:
    • Value не является простым объектом
    • Свойства не соответсвуют переданной схеме валидации
  • Возвращаем объект ошибок, соответсвующих ошибкам для свойств объекта
  • Требует схему для валидации, свойства которой должны соответствовать валидируемому values
import {
  object,
  array,
  arrayItem,
  string,
  optional,
  min,
  number,
  toPrettyError
} from '@astral/validations';

type User = {
  name: string;
  surname?: string;
  info: {
    permissions: Permission[];
  };
};

const validate = object<User>({
  name: string(),
  surname: optional(string()),
  info: object<User['info']>({
    permissions: array(
      arrayItem(
        object<Permission>({
          id: number(),
          description: string(min(2)),
        }),
      ),
    ),
  }),
});

// undefined
validate({
  name: 'Vasya',
  info: {
    permissions: [{ id: 1, description: 'my permission' }],
  },
});

// { info: { permissions: [{ description: 'Обязательно' }] } }
toPrettyError(
  validate({
    name: 'Vasya',
    info: {
      permissions: [{ id: 1 }],
    },
  })
);

partial

Позволяет сделать все поля объекта optional.

import { partial, object, string, toPrettyError } from '@astral/validations';

type Values = {
  name: string;
  surname: string;
};

const validateRequired = object<Values>({
  name: string(),
  surname: string()
})

// { name: 'Обязательно' }
toPrettyError(
  validateRequired({})
);

const validatePartial = partial(
  object<Values>({
    name: string(),
    surname: string()
  })
);

// undefined
validatePartial({});

deepPartial

Позволяет сделать гулбокий partial для свойсв всех объектов в схеме, включая объекты в массиве.

import {
  object,
  array,
  arrayItem,
  string,
  deepPartial,
  min,
  number
} from '@astral/validations';

type Permission = {
  id: number;
  description: string;
};

type User = {
  name: string;
  surname?: string;
  info: {
    permissions: Permission[];
  };
};

const validate = deepPartial(
  object<User>({
    name: string(),
    surname: optional(string()),
    info: object<User['info']>({
      permissions: array(
        arrayItem(
          object<Permission>({
            id: number(),
            description: string(min(2)),
          }),
        ),
      ),
    }),
  })
);

// undefined
validate({
  info: {
    permissions: [{}]
  },
});

array

  • Позволяет валидировать array
  • Возвращает ошибку если:
    • Value не является array
  • Выполняет композицию правил, переданных в параметры
import {
  array,
  arrayItem,
  min,
  toPrettyError
} from '@astral/validations';

type User = {
  name: string;
  surname?: string;
};

const validate = array(
  min(1),
  arrayItem(
    object<User>({
      name: string(),
      surname: optional(string()),
    }),
  ),
);

// undefined
validate([{ name: 'Vasya' }]);

// { message: 'Не меньше: 1' }
validate([]);

// [{ name: 'Не является строкой' }]
toPrettyError(
  validate([{ name: 22 }])
);

arrayItem

Применяет переданные правила валидации к каждому элементу массива.

import { array, arrayItem, object, string, optional, toPrettyError } from '@astral/validations';

type User = {
  name: string;
  surname?: string;
};

const validate = array(
  arrayItem(
    object<User>({
      name: string(),
      surname: optional(string()),
    }),
  ),
);

// undefined
validate([{ name: 'Vasya' }]);

// { cause: { errorArray: [{ name: { message: 'Не является строкой' } }] } }
toPrettyError(
  validate([{ name: 22 }])
);
import { array, arrayItem, string, min, toPrettyError } from '@astral/validations';

const validate = array(arrayItem(string(min(3))));

// [undefined, 'Мин. символов: 3']
toPrettyError(
  validate(['vasya', 'ma'])
);

min array

Позволяет указать ограничение на минимальное количество элементов в массиве.

import { array, min } from '@astral/validations';

const validate = array(min(1));

// { message: 'Не меньше: 1' }
validate([]);

// undefined
validate([1, 2]);

max array

Позволяет указать ограничение на максимальное количество элементов в массиве.

import { array, max } from '@astral/validations';

const validate = array(max(3));

// { message: 'Не больше: 3' }
validate([1,2,3,4]);

// undefined
validate([1, 2]);

any

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

type Values = { name: string; isAgree: boolean };

const validate = object<Values>({
  name: when({
    is: (_, ctx) => Boolean(ctx.values?.isAgree),
    then: string(),
    otherwise: any(),
  }),
  isAgree: optional(boolean()),
});

// undefined
validate({ isAgree: false, name: '' });

// { name: 'Обязательно' }
toPrettyError(
  validate({ isAgree: true, name: '' })
);
  const validate = any(transform((value) => new Date(value), date()));

// undefined
validate('12.22.2022');

// invalid date error
validate('13.22.2022');

Define. Переопределение дефолтных параметров guard

Каждый guard позволяет переопределить дефолтные параметры:

  • Сообщение об ошибке типа
  • Сообщение об ошибке required
  • Уникальные для каждого guard параметры
import { string } from '@astral/validations';

const validateCustomString = string().define({
  typeErrorMessage: 'Только строка',
  requiredErrorMessage: 'Не может быть пустым',
});

// { message: 'Не может быть пустым' }
validateCustomString(undefined);

// { message: 'Только строка' }
validateCustomString(20);

Custom rules

Каждый guard поддерживает кастомные правила.

Базовый пример

import { string, object, toPrettyError } from '@astral/validations';

type Values = {
  name: string;
  nickname: string;
};

const validate = object<Values>({
  name: string(),
  nickname: string((value, ctx) => {
    if (value.includes('_')) {
      return ctx.createError({
        message: 'Символ "_" запрещен',
        code: 'nickname-symbol',
      });
    }

    return undefined;
  }),
});

// { nickname: 'Символ "_" запрещен' }
toPrettyError(
  validate({ name: 'Vasya', nickname: 'va_sya' })
);

Связанные поля

В ctx.values находится value, принятое последним object.

import { object, string, toPrettyError } from '@astral/validations';

type Values = {
  password: string;
  repeatPassword: string;
};

const validate = object<Values>({
  password: string(min(9)),
  repeatPassword: string(min(9), (value, ctx) => {
    if (value !== ctx.values?.password) {
      return ctx.createError({
        message: 'Пароли не совпадают',
        code: 'repeat-password',
      });
    }

    return undefined;
  }),
});

// { repeatPassword: 'Пароли не совпадают' } 
toPrettyError(
  validate({ password: 'qywerty123', repeatPassword: 'qywerty1234' })
);

Доступ к высокоуровневым values (ctx.global.values)

В ctx.global.values находится values, полученное самым первым guard.

import { object, string, boolean, optional } from '@astral/validations';

type Values = {
  isAgree: boolean;
  info: {
    name: string
  }
};

const validate = object<Values>({
  isAgree: optional(boolean()),
  info: object<Values['info']>({
    name: when({
      is: (_, ctx) => Boolean(ctx.global.values?.isAgree),
      then: string(),
      otherwise: any(),
    }),
  })
});

Переиспользуемое правило

import { createRule, string } from '@astral/validations';

type Params = {
  message?: string;
};

const includesWorld = <TValues>(params: Params) =>
  createRule<string, TValues>((value, ctx) => {
    if (value.includes('world')) {
      return undefined;
    }

    return ctx.createError({
      message: params?.message || 'Должен содержать "world"',
      code: 'includes-word',
    });
  });

const validate = string(includesWorld());

// undefined
validate('Hello world');

// { message: 'Должен содержать "world"' } 
validate('Hello');

// { message: 'Должен содержать "world"' } 
includesWorld()('Hello')

Кастомная условная валидация

Для условной валидации рекомендуется использовать when, но также доступна возможность реализации кастомной условной валидации.

import { object, string, boolean, optional } from '@astral/validations';

type Values = {
  isAgree: boolean;
  info: {
    name: string
  }
};

const validate = object<Values>({
  isAgree: optional(boolean()),
  info: object<Values['info']>({
    name: (value, ctx) => {
      if(ctx.global.values?.isAgree) {
        return string();
      }
      
      return any();
    }
  })
});

Common

optional

Выключает дефолтную проверку на required в guard.

import { optional, object, string, boolean, array } from '@astral/validations';

type Values = {
  name: string;
  surname?: string;
  permissions?: number[];
  isAuth?: boolean;
};

const validate = object<Values>({
  name: string(),
  surname: optional(string()),
  permissions: optional(array(string())),
  isAuth: optional(boolean()),
})

// undefined
validate({
  name: 'Vasya',
  surname: '',
  isAuth: false,
});

Позволяет делать optional вложенные правила:

type Values = { name: string | number; isAgree: boolean };

const validate = object<Values>({
  name: optional(
    when({
      is: (_, ctx) => Boolean(ctx.values?.isAgree),
      then: string(),
      otherwise: number(),
    })
  ),
  isAgree: optional(boolean()),
});

// undefined
validate({ isAgree: false, name: undefined });

when. Условная валидация

Позволяет определять условные валидации.

type Values = { name: string; isAgree: boolean };

const validate = object<Values>({
  name: when({
    is: (_, ctx) => Boolean(ctx.values?.isAgree),
    then: string(),
    otherwise: any(),
  }),
  isAgree: optional(boolean()),
});

// undefined
validate({ isAgree: false, name: '' });

// { name: 'Обязательно' }
toPrettyError(
  validate({ isAgree: true, name: '' })
);

When для ветки объекта:

type ValuesInfo = { surname: string };

type Values = {
  name: string;
  info?: ValuesInfo;
};

const validate = object<Values>({
  name: string(),
  info: when({
    is: (_, ctx) => ctx.values?.name === 'Vasya',
    then: object<ValuesInfo>({ surname: string() }),
    otherwise: any(),
  }),
});

// { info: 'Обязательно' }
toPrettyError(
  validate({ name: 'Vasya' })
);

// undefined
validate({ name: 'Kolya' });

enabled. Условная валидация

Позволяет определять условные валидации без выбора альтернативной схемы. Является упрощением when с otherwise = any().

type Values = { name: string; isAgree: boolean };

const validate = object<Values>({
  name: enabled({
    is: (_, ctx) => Boolean(ctx.values?.isAgree),
    then: string(),
  }),
  isAgree: optional(boolean()),
});

// undefined
validate({ isAgree: false, name: '' });

// { name: 'Обязательно' }
toPrettyError(
  validate({ isAgree: true, name: '' })
);

Enabled для ветки объекта:

type ValuesInfo = { surname: string };

type Values = {
  name: string;
  info?: ValuesInfo;
};

const validate = object<Values>({
  name: string(),
  info: enabled({
    is: (_, ctx) => ctx.values?.name === 'Vasya',
    then: object<ValuesInfo>({ surname: string() }),
  }),
});

// { info: 'Обязательно' }
toPrettyError(
  validate({ name: 'Vasya' })
);

// undefined
validate({ name: 'Kolya' });

transform

Позволяет изменять value в цепочке композиции.

import { transform, date, min } from '@astral/validations';

const validate = string(
  transform((value) => new Date(value), date(min(new Date()))),
);

// { message: 'Некорректная дата' }
validate('22.22.2022');

// undefined
validate('12.12.2022');

or

Выполняет переданные правила аналогично оператору ||. Если одно из правил не завершилось ошибкой, то or вернет undefined. Если все переданные правила завершились с ошибкой, то вернется ошибка из последнего правила

import { or, array, string, number } from '@astral/validations';

const validate = or(string(), array(), number());

// undefined
validate('string')

// undefined
validate([])

// undefined
validate(20)

// { message: 'Не число' }
validate(new Date())

Async

Пакет поддерживает асинхронную валидацию.

Guard, поддерживающие асинхронную валидацию имеют постфиксы async:

  • objectAsync
  • stringAsync
  • optionalAsync

Пример:

type Values = {
    nickname: string;
    phone: string;
};

const validate = objectAsync<Values>({
    phone: string(),
    nickname: stringAsync(min(3), async (value, ctx) => {
        const nicknameIsAvailable = await checkNickname(value);

        if (nicknameIsAvailable) {
            return undefined;
        }

        return ctx.createError({
            code: 'nickname-available',
            message: 'Nickname занят',
        });
    }),
    fullName: optionalAsync(stringAsync(async (value, ctx) => {
        const nicknameIsAvailable = await checkNickname(value);

        if (nicknameIsAvailable) {
            return undefined;
        }

        return ctx.createError({
            code: 'nickname-available',
            message: 'Nickname занят',
        });
    })),
});

const result = await validate({ phone: '79308999999', nickname: 'Vasya', fullName: '' });

// { nickname: 'Nickname занят' }
toPrettyError(result);

Integrations

react-hook-form

Для интеграции с react-hook-form необходимо использовать пакет @astral/validations-react-hook-form-resolver.

Codesandbox

Basic usage

import { object, string, optional } from '@astral/validations';
import { resolver } from '@astral/validations-react-hook-form-resolver';
import { useForm } from 'react-hook-form';

type Values = {
    name: string;
    info: { description?: string }
};

const validationSchema = object<Values>({
  name: string(),
  info: object<Values['info']>({
    description: optional(string()),
  }),
});

const Form = () => {
  const { register, handleSubmit, formState } = useForm<Values>({
    resolver: resolver<Values>(validationSchema),
  });

  return (
    <form onSubmit={handleSubmit(() => {})}>
      <input {...register('name')} />
      {formState.errors.name && (
        <p>{formState.errors.name.message}</p>
      )}
      <input {...register('info.description')} />
      {formState.errors.info?.description && (
        <p>{formState.errors.info.description.message}</p>
      )}
      <button type="submit">submit</button>
    </form>
  );
};

Переиспользуемый useForm

import { ObjectGuard, object, optional, string } from '@astral/validations';
import { resolver } from '@astral/validations-react-hook-form-resolver';
import {
  FieldValues,
  UseFormReturn,
  UseFormProps as UseReactHookFormProps,
  useForm as useReactHookForm,
} from 'react-hook-form';

type UseFormProps<TFieldValues extends FieldValues = FieldValues> = Omit<
  UseReactHookFormProps<TFieldValues>,
  'resolver'
> & {
  validationSchema?: ObjectGuard<TFieldValues, TFieldValues>;
};

const useForm = <TFieldValues extends FieldValues = FieldValues>({
  validationSchema,
  defaultValues,
  ...params
}: UseFormProps<TFieldValues>): UseFormReturn<TFieldValues> =>
  useReactHookForm<TFieldValues>({
    ...params,
    defaultValues,
    resolver: validationSchema && resolver(validationSchema),
  });

type Values = {
  name: string;
  info: { description?: string };
};

const validationSchema = object<Values>({
  name: string(),
  info: object<Values['info']>({
    description: optional(string()),
  }),
});

const Form = () => {
  const { register, handleSubmit, formState } = useForm<Values>({
    validationSchema,
  });

  return (
    <form onSubmit={handleSubmit(() => {})}>
      <input {...register('name')} />
      {formState.errors.name && <p>{formState.errors.name.message}</p>}
      <input {...register('info.description')} />
      {formState.errors.info?.description && (
        <p>{formState.errors.info.description.message}</p>
      )}
      <button type="submit">submit</button>
    </form>
  );
};

Guides

Переиспользование объектов схемы

type Address = {
  street: string;
};

const address = object<Address>({ street: string() });

type Organization = {
  address: Address;
};

const organization = object<Organization>({ address });

type Values = {
  name: string;
  org: Organization;
};

const validateValues = object<Values>({
  name: string(),
  org: organization,
});

Переиспользование объектов схемы, с условной валидацией и зависимыми полями

type RusOrganization = {
  inn: string;
  isIP: boolean;
};

type EngOrganization = {
  name: string;
};

type Values = {
  isRus: boolean;
  org: { data: RusOrganization | EngOrganization };
};

const rusOrganization = object<RusOrganization>({
  inn: string(
    when({
      is: (_, ctx) => Boolean((ctx.global.values as Values)?.isRus),
      then: rusOrganization,
      otherwise: engOrganization,
    }),
  ),
  isIP: optional(boolean()),
});

const engOrganization = object<EngOrganization, Values>({ name: string() });

const organization = when<Values>({
  is: (_, ctx) => Boolean((ctx.global.values as Values)?.isRus),
  then: rusOrganization,
  otherwise: engOrganization,
});

const validate = object<Values>({
  isRus: optional(boolean()),
  org: organization,
});

Error message customization

Сообщения об ошибках по умолчанию могут быть заменены на пользовательские.
Для этого необходимо использовать параметры message или getMessage у валидационных методов:

//getMessage
const validateMin = number(min(10, {
    getMessage: (threshold, value, ctx) => {
        return `Слишком мало, минимум ${threshold}`
    }
}));
// { message: 'Слишком мало, минимум 10' }
validateMin(5);

//message
const validateKPP = string(kpp({ message: 'Что-то не так с кодом КПП' }));
// { message: 'Что-то не так с кодом КПП' }
validateKPP('123123');

Exclusion managing

Метод exclude предоставляет возможность обхода валидации для конкретного значения.
Если функция вернет true, текущее значение не будет провалидировано, метод валидации вернет undefined.

Пример реализации:

//значение для обхода валидации (исключение)
const excludeValue = '0101010101';
//функция для обработки исключения
const isExclude = (value: string) => {
  const excluded: string[] = [excludeValue];

  return excluded.includes(value);
};

const validate = string(kpp({ exclude: isExclude }));
// undefined (значение не будет провалидировано)
validate(excludeValue);

Migration guide

v3 -> v4

object

Generic object guard стал принимать один параметр - валидируемое значение.

Типизация guard и rules

Generics правил и guards стали принимать тип для ctx.values вместо ctx.global.values.

ctx.global.values

ctx.global.values стал unknown. Для использования необходимо вручную уточнять тип. Пример.