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 🙏

© 2026 – Pkg Stats / Ryan Hefner

vuevaldi

v0.14.0

Published

Reactive form validation library for Vue 3 with yup

Readme

vuevaldi

Библиотека валидации форм для Vue 3.

TypeScript-first, агностик к валидатору (yup работает из коробки), реактивная, событийно-ориентированная.

🌐 Language / Язык: English · Русский


Возможности

  • Агностик к валидатору: подходит любой валидатор, реализующий интерфейс FormValidator; адаптер для yup включён.
  • Ошибки структурно повторяют модель формы (вложенные объекты), внутри — плоские пути.
  • Метки (labels) и значения по умолчанию берутся из самой схемы (yup .label() / .default()).
  • Перевалидация при вводе (validateOnInput) — до submit показываются ошибки только изменённых полей.
  • События submit: success, error, finished.
  • Маппинг серверных ошибок через errorHandler + violations.
  • Написано на TypeScript со строгой типизацией полей, ошибок, меток и дефолтов.

Установка

npm install vuevaldi yup

vue и yup — peer-зависимости. Пакет только ESM, типы включены.


Быстрый старт

Объявляем yup-схему, создаём валидатор через useYupValidator и форму через useForm.

<script setup lang="ts">
import * as yup from 'yup';
import { useForm, useYupValidator } from 'vuevaldi';

const schema = yup.object({
    name: yup.string().required('Имя обязательно').label('Имя'),
    email: yup.string().email('Некорректный email').required('Email обязателен').label('Email'),
    age: yup.number().min(18, 'Должно быть 18+').label('Возраст'),
});

const validator = useYupValidator(schema);

const { model, error, errors, labels, isSubmitting, submit } = useForm({
    validator,
    validateOnInput: true,
    submitHandler: async values => {
        await api.save(values);
    },
});
</script>

<template>
    <form @submit.prevent="submit()">
        <p v-if="error" class="form-error">{{ error }}</p>

        <div>
            <label>{{ labels.name }}</label>
            <input v-model="model.name" />
            <span v-if="errors.name" class="field-error">{{ errors.name[0] }}</span>
        </div>

        <div>
            <label>{{ labels.email }}</label>
            <input v-model="model.email" />
            <span v-if="errors.email" class="field-error">{{ errors.email[0] }}</span>
        </div>

        <div>
            <label>{{ labels.age }}</label>
            <input v-model.number="model.age" />
            <span v-if="errors.age" class="field-error">{{ errors.age[0] }}</span>
        </div>

        <button type="submit" :disabled="isSubmitting">Отправить</button>
    </form>
</template>

model, errors, labels и остальное — Vue-рефы / computed-рефы. При деструктуризации в <script setup> они автоматически разворачиваются в шаблоне. Если держать весь объект целиком (const form = useForm(...)), обращайтесь через .value: form.model.value.name.

submit() сначала валидирует; при успехе вызывает submitHandler(values) и возвращает true, иначе — false.


API — useForm(opt)

Поля FormContextOptions (все опциональны, кроме validator и submitHandler):

| Опция | Тип | Описание | |---|---|---| | values | PartialObjectDeep<TFields> | Реальные начальные данные (например, загруженная запись). Используются как есть, без merge со schema-defaults. | | defaultValues | PartialObjectDeep<TFields> | Шаблон начального/сброшенного состояния. Глубоко сливается поверх schema-defaults (schema дозаполняет, эти переопределяют). | | extraData | TExtraData | Произвольные дополнительные данные, доступные через getExtraData(). | | submitHandler | (values: TFields) => TResp \| Promise<TResp> | Обязателен. Вызывается при успешной валидации. | | errorHandler | (error: TErr) => { message: string; violations?: Violation[] } | Преобразует ошибку submit в сообщение и ошибки полей. | | resetAfterSubmit | boolean | Сбросить форму после успешного submit. | | validateOnInput | boolean | Перевалидировать при изменении модели; до первого submit показываются ошибки только изменённых полей. | | validator | FormValidator<TFields> | Обязателен. Реализация валидатора. |

type Violation = { message: string; propertyPath: string };

Приоритет начальной модели:

values ?? merge(schemaDefaults, defaultValues) ?? {}
  • values выигрывают целиком (schema не подмешивается — реальные данные не трогаются).
  • Иначе база — schema-defaults, поверх глубоко сливается defaultValues.
  • Массивы заменяются целиком, не сливаются по индексу.

API — возвращаемый контекст

| Поле | Тип | Описание | |---|---|---| | model | Ref<PartialObjectDeep<TFields>> | Реактивное состояние формы; биндьте инпуты через v-model. | | error | ComputedRef<string> | Глобальная ошибка формы (submit / сервер). | | errors | ComputedRef<ValidationErrors<TFields>> | Ошибки полей, структурно повторяющие модель. | | labels | ComputedRef<FormLabels<TFields>> | Метки полей из схемы, структурно повторяющие модель. | | isSubmitting | ComputedRef<boolean> | true, пока идёт submit(). | | isDirty | ComputedRef<boolean> | true, если модель отличается от исходного состояния (на момент создания формы или последнего reset()). Сравнение всегда идёт по текущим данным, поэтому ручной возврат значений к исходным (в т.ч. по отдельному полю) тоже сбрасывает флаг в false — не только reset(). | | submit | (throwError?: boolean) => Promise<boolean> | Валидация → вызов submitHandler. Возвращает успех. С throwError бросает Error(error) при неудаче. | | reset | (newOpt?: { values?, defaultValues? }) => void | Сброс ошибок и модели (с merge schema-defaults как выше). | | validate | () => Promise<false \| TFields> | Валидирует текущую модель; возвращает распарсенные значения или false. | | addEventListener | (event, listener) => void | Подписка на success / error / finished. | | getExtraData | () => TExtraData | Возвращает extraData; бросает ошибку, если не задано. |

AnyFormContext — слаботипизированная версия того же контракта (для передачи контекста куда-либо без типов).


Валидатор

Интерфейс FormValidator

Любой валидатор для useForm должен реализовать этот контракт:

interface FormValidator<TFields extends Recordable = Recordable> {
    isValid: (values: PartialObjectDeep<TFields>) => Promise<boolean>;
    parse: (values: PartialObjectDeep<TFields>) => Promise<
        | { isError: false; values: TFields; errors: undefined }
        | { isError: true; values: undefined; errors: FlattenedErrors }
    >;
    describe?: () => FormSchemaInfo<TFields>;
}
  • errorsплоская карта: Record<string, string[]> (пути вида 'address.city'). Форма разворачивает её во вложенный реф errors.
  • describe опционален и питает labels + schema-defaults.
  • FlattenedErrors = Record<string, string[]>.

useYupValidator(schema, options?)

Оборачивает yup ObjectSchema в FormValidator.

import { useYupValidator } from 'vuevaldi';

const schema = yup.object({
    name: yup.string().required().label('Имя'),
    age: yup.number().min(18).label('Возраст'),
});

const validator = useYupValidator(schema, {
    // ValidateOptions (yup): strict, abortEarly, stripUnknown, recursive...
});

Дефолты: strict: false, abortEarly: false, stripUnknown: false, recursive: true, disableStackTrace: true.

Работает со вложенными объектами, массивами, кортежами, .when() и т.д. — ошибки раскладываются по путям полей и затем собираются обратно в структуру модели.

const schema = yup.object({
    profile: yup.object({
        firstName: yup.string().required().label('Имя'),
        lastName: yup.string().required().label('Фамилия'),
    }),
    tags: yup.array().of(yup.string()),
    items: yup.array().of(yup.object({
        sku: yup.string().required().label('Артикул'),
        qty: yup.number().min(1).label('Количество'),
    })),
});

errors.profile.firstNamestring[], errors.items[{ sku?: string[]; qty?: string[] }].


Метки и дефолты из схемы

useYupValidator отдаёт describe(), построенный из yup schema.describe(), который наполняет две вещи:

  • labels — метки из .label(), структурно как errors/модель:

    • поля-листья → string | undefined;
    • поля-объекты → вложенный объект меток; собственный label объекта доступен по зарезервированному ключу $label (labels.money.$label);
    • массивы примитивов → метка элемента (с фолбэком на метку массива);
    • массивы объектов → массив с одним элементом = метки элемента (labels.items[0].sku); метка самого массива лежит на массиве (labels.items.$label), метка объекта-элемента — labels.items[0].$label.
  • defaults — значения из .default(), база для начальной модели и reset() (сливаются под defaultValues, см. выше).

const schema = yup.object({
    name: yup.string().default('Иван').label('Имя'),
    address: yup.object({
        city: yup.string().default('Москва').label('Город'),
    }).label('Адрес'),
});

const { labels, model } = useForm({
    validator: useYupValidator(schema),
    submitHandler: async () => {},
});

// labels: { name: 'Имя', address: { $label: 'Адрес', city: 'Город' } }
// labels.address.$label → 'Адрес'
// model:  { name: 'Иван', address: { city: 'Москва' } }

Кастомные валидаторы могут реализовать describe?: () => FormSchemaInfo<TFields>, чтобы тоже отдавать labels и defaults.


События

Подписка через addEventListener; каждый вызов регистрирует ещё один слушатель.

form.addEventListener('success', data => console.log('Сохранено', data));
form.addEventListener('error', err => console.error('Ошибка', err));
form.addEventListener('finished', () => console.log('Готово'));

| Событие | Payload | Когда | |---|---|---| | success | TResp — возвращаемое значение submitHandler | После успешного submit | | error | TErr — ошибка валидации ('Validation failed') или ошибка из submitHandler | При неудачной валидации или submit | | finished | — | Всегда после попытки submit |


Ошибки при submit

submit() сначала валидирует. При неудачной валидации error получает 'Validation failed', срабатывает событие error, submit() возвращает false. Если валидация прошла — ожидается submitHandler(values):

  • при успехе — событие success (+ сброс, если resetAfterSubmit), возвращается true;
  • при ошибке — вызывается errorHandler(error) (если передан), иначе error.value = error.message для инстансов Error.

errorHandler преобразует ошибку в сообщение и ошибки полей, обычно из ответа бэкенда:

import type { AxiosError } from 'axios';

const schema = yup.object({
    name: yup.string().required().label('Имя'),
    email: yup.string().required().label('Email'),
});

type UserForm = yup.InferType<typeof schema>;

interface ApiError {
    message: string;
    errors: { field: string; message: string }[];
}

const form = useForm<UserForm, void, AxiosError<ApiError>>({
    validator: useYupValidator(schema),
    submitHandler: values => api.save(values),
    errorHandler: e => ({
        message: e.response?.data.message ?? 'Запрос не удался',
        violations: e.response?.data.errors.map(v => ({
            message: v.message,
            propertyPath: v.field, // например 'name' или 'address.city'
        })),
    }),
});

Violations с propertyPath попадают в ту же вложенную структуру errors, что и ошибки клиентской валидации.


Опции подробнее

validateOnInput

При включении форма глубоко следит за моделью, перевалидирует при каждом изменении и до первого submit показывает ошибки только изменённых полей. После неудачного submit показываются все ошибки.

resetAfterSubmit

Сбрасывает форму (ошибки + модель к дефолтам) после успешного вызова submitHandler.

extraData / getExtraData

Произвольные метаданные, переданные при создании и читаемые через getExtraData() (типизированы как TExtraData). Бросает ошибку, если extraData не передан.

values vs defaultValues

  • values — реальные данные, используются как есть, без merge со schema-defaults.
  • defaultValues — шаблон, глубоко сливается поверх schema-defaults.
  • Не передавать ничего — форма инициализируется только из schema-defaults.

Кастомный валидатор

Подойдёт любой объект, соответствующий FormValidator. Минимальный ручной валидатор:

import type { FormValidator } from 'vuevaldi';

type UserForm = {
    name: string;
    age?: number;
};

const customValidator: FormValidator<UserForm> = {
    isValid: async values => Boolean(values.name && values.name.length >= 2),

    parse: async values => {
        const errors: Record<string, string[]> = {};

        if (!values.name) {
            errors.name = ['Имя обязательно'];
        } else if (values.name.length < 2) {
            errors.name = ['Имя должно быть не короче 2 символов'];
        }

        if (values.age !== undefined && values.age < 18) {
            errors.age = ['Должно быть 18+'];
        }

        if (Object.keys(errors).length > 0) {
            return { isError: true, values: undefined, errors };
        }

        return { isError: false, values: values as UserForm, errors: undefined };
    },
};

const form = useForm<UserForm>({
    validator: customValidator,
    submitHandler: values => api.save(values),
});

Опционально реализуйте describe(), чтобы тоже отдавать labels и schema-defaults:

import type { FormSchemaInfo } from 'vuevaldi';

const customValidator: FormValidator<UserForm> = {
    // isValid, parse ...

    describe: (): FormSchemaInfo<UserForm> => ({
        labels: { name: 'Имя', age: 'Возраст' },
        defaults: { name: 'Иван' },
    }),
};

Экспортируемые типы

import type {
    AnyFormContext,     // слаботипизированный контекст
    FormContext,        // типизированный контекст
    FormContextOptions, // опции useForm
    FormSchemaInfo,     // { labels?, defaults? } из validator.describe
    FormValidator,      // контракт валидатора
    FormLabels,         // структура меток
    PartialObjectDeep,  // глубокий partial полей (массивы не рекурсятся)
    ValidationErrors,   // структура ошибок
} from 'vuevaldi';

Лицензия

MIT © ТОО «ADEGARA».