Библиотека Joi развивалась как часть экосистемы Hapi и постепенно превратилась в самостоятельный инструмент валидации схем для JavaScript. При переходе между мажорными версиями происходили изменения, несовместимые с предыдущими реализациями. Эти изменения затрагивали не только публичный API, но и внутреннюю модель работы с данными, формат ошибок, поведение валидаторов и систему компоновки схем.
Основная причина появления breaking changes заключалась в стремлении:
@hapi/joi к joi: изменение пространства
имёнОдним из значимых переломных моментов стало разделение библиотеки на
отдельный пакет joi. Ранее использовался scoped-пакет
@hapi/joi, тесно связанный с экосистемой Hapi.
Изменения:
// старый вариант
const Joi = require('@hapi/joi');
// новый вариант
const Joi = require('joi');
Переход не ограничивался косметическим изменением импорта — внутренняя логика обработки схем была переработана, что повлияло на поведение ряда методов.
Одним из ключевых источников несовместимости стали изменения в механике валидации.
Ранее Joi мог выполнять неявные преобразования типов. В новых версиях поведение стало более предсказуемым и строгим.
Joi.number().validate('5'); // ранее: 5
// теперь: ошибка или требуется включение convert
Поведение стало зависеть от опции convert, которая
контролирует приведение типов.
presence и обязательностиРанее использование required() имело менее строгую
модель проверки. В новых версиях:
required, optional,
forbidden унифицированоobjectconst schema = Joi.object({
a: Joi.string()
}).required();
В новых версиях обязательность объекта не всегда автоматически распространяется на вложенные поля.
Механизм alternatives() был переработан:
const schema = Joi.alternatives().try(
Joi.string(),
Joi.number()
);
Ранее возможны были неоднозначные результаты при пересечении условий, теперь приоритет вычисляется более строго.
Существенное изменение связано с разделением синхронной и асинхронной валидации.
validate() остаётся синхроннымvalidateAsync() стал основным способом работы с
асинхронными правиламиconst result = await schema.validateAsync(data);
В старых версиях асинхронное поведение могло быть скрыто внутри
validate, что приводило к неоднозначности.
Некоторые методы были удалены или заменены:
Joi.validate() как статическая функцияJoi.int и подобные)Цепочки методов стали более строгими:
Пример:
Joi.string().min(3).max(10).required();
Теперь порядок и совместимость модификаторов проверяются более жёстко.
Формат ошибок был переработан:
detailscontext{
"message": "...",
"details": [
{
"message": "...",
"path": ["field"],
"type": "string.min"
}
]
}
Ранее структура могла различаться между типами валидаторов.
Введена более строгая система кодов:
any.required,
string.base)abortEarlyПоведение опции стало более предсказуемым:
abortEarly: true возвращается первая ошибкаfalse — полный список без частичных пропусковРанее возможны были случаи неполного сбора ошибок в сложных схемах.
concatПоведение concat() было уточнено:
const base = Joi.object({ a: Joi.string() });
const extended = base.concat(Joi.object({ b: Joi.number() }));
object
схемыИзменена обработка вложенных структур:
unknown()Механизм Joi.ref() получил обновлённую семантику:
Joi.object({
a: Joi.number(),
b: Joi.ref('a')
});
Ранее возможны были случаи неоднозначного разрешения пути.
trim()pattern() при флагах регулярных
выраженийprecision()Поведение стало более предсказуемым:
Миграция между версиями требует пересмотра следующих аспектов:
validate() vs
validateAsync()alternatives()required() на вложенных объектахОсобое внимание требуется схемам, которые опирались на:
В новых версиях:
any в типах APIОднако часть старых деклараций стала несовместимой:
validateobject() и array()
схем