Другое

Исправление ошибки Joi AssertError: Must be invoked on a Joi instance

Узнайте, как исправить Joi AssertError: Must be invoked on a Joi instance в проектах TypeScript. Полное руководство с использованием namespace imports, настройки TypeScript и лучших практик для Joi v18.0.1.

Joi AssertError: “Должна вызываться для экземпляра Joi” в проекте TypeScript

Я столкнулся с ошибкой AssertError в своем проекте TypeScript при использовании Joi v18.0.1. Сообщение об ошибке:

throw new AssertError(msgs.join(' '), assert);
    ^

AssertError: Must be invoked on a Joi instance.

Определения схем Joi

Вот определения схем, которые я использую:

typescript
import Joi from "joi";
import {env} from "#services/env";

export const AUTHENTICATE_ACCOUNT_SCHEMA = Joi.object({
    username: Joi.string().min(4).max(20),
    email: Joi.string()
        .email({tlds: {allow: env.ALLOW_TLDS_EMAIL_VALIDATION === "true"}})
        .min(4)
        .max(40),
    password: Joi.string().min(4).max(20).required(),
    captcha: Joi.string().required(),
}).required();

export const GET_ACCOUNT_SCHEMA = Joi.object({
    accountId: Joi.number().min(0).required(),
});

export const DELETE_ACCOUNT_SCHEMA = Joi.object({
    accountId: Joi.number().min(0).required(),
});

export const SEARCH_ACCOUNTS_SCHEMA = Joi.object({
    searchText: Joi.string(),
});

export const CREATE_ACCOUNT_SCHEMA = Joi.object({
    email: Joi.string().email(),
    username: Joi.string().min(4).max(20).required(),
    password: Joi.string().min(4).max(20).required(),
    role: Joi.string().valid("default", "teacher").required(),
    captcha: Joi.string().required(),
}).required();

export const UPDATE_ACCOUNT_SCHEMA = Joi.object({
    accountId: Joi.number().min(0),
    balance: Joi.number(),
    role: Joi.string().valid("root", "default", "teacher"),
    blocked: Joi.boolean(),
    email: Joi.string().email(),
    username: Joi.string().min(4).max(20),
    password: Joi.string().min(4).max(20),
})
    .min(1)
    .required();

Что я уже пробовал

Я уже проверил GitHub issue #1349, но это не помогло решить мою проблему. Я также пробовал получать ответы от LLMs безуспешно.

Детали окружения

  • Версия Node.js: 24.5.0
  • Версия Joi: 18.0.1
  • Проект на TypeScript

Вопрос

Как исправить ошибку “AssertError: Must be invoked on a Joi instance” в моем проекте TypeScript с использованием Joi v18.0.1?

Ошибка “AssertError: Must be invoked on a Joi instance” в Joi v18.0.1

Ошибка “AssertError: Must be invoked on a Joi instance” в Joi v18.0.1 обычно возникает из-за изменений в том, как Joi обрабатывает импорты и цепочки вызовов методов в TypeScript. Эта ошибка происходит, когда методы Joi вызываются без правильного контекста или при неправильном использовании деструктурирующих импортов. Наиболее распространенным решением является изменение подхода к импорту и обеспечение правильной конфигурации TypeScript.

Содержание


Основные причины ошибки

Ошибка “Must be invoked on a Joi instance” обычно вызвана одной из следующих проблем:

1. Деструктурирующие импорты

При деструктуризации методов Joi, такой как import { string, object } from 'joi', методы теряют свой контекст и не могут правильно получить доступ к экземпляру Joi. Это является критическим изменением в Joi v18.0.1.

2. Проблемы с импортом по умолчанию

Использование import Joi from "joi" иногда вызывает проблемы с привязкой методов, где методы Joi не правильно привязаны к экземпляру.

3. Несоответствие типов TypeScript

Определения типов могут быть неправильно согласованы с поведением Joi во время выполнения, что вызывает ошибки утверждения во время проверки.

Немедленные решения

Решение 1: Использование импорта по пространству имен

Замените текущий оператор импорта на импорт по пространству имен:

typescript
import * as Joi from "joi";

Затем обновите определения схем, используя префикс пространства имен:

typescript
export const AUTHENTICATE_ACCOUNT_SCHEMA = Joi.object({
    username: Joi.string().min(4).max(20),
    email: Joi.string()
        .email({tlds: {allow: env.ALLOW_TLDS_EMAIL_VALIDATION === "true"}})
        .min(4)
        .max(40),
    password: Joi.string().min(4).max(20).required(),
    captcha: Joi.string().required(),
}).required();

Решение 2: Использование именованных импортов с явной привязкой

Если вы предпочитаете именованные импорты, привяжите методы к экземпляру Joi:

typescript
import Joi from "joi";

// Привязка методов к экземпляру
const { object, string, number, boolean } = Joi;

export const AUTHENTICATE_ACCOUNT_SCHEMA = object({
    username: string().min(4).max(20),
    email: string()
        .email({tlds: {allow: env.ALLOW_TLDS_EMAIL_VALIDATION === "true"}})
        .min(4)
        .max(40),
    password: string().min(4).max(20).required(),
    captcha: string().required(),
}).required();

Решение 3: Использование Joi.create() для сложных схем

Для более сложных сценариев используйте метод Joi.create():

typescript
import Joi from "joi";

export const AUTHENTICATE_ACCOUNT_SCHEMA = Joi.create()
    .object({
        username: Joi.string().min(4).max(20),
        email: Joi.string()
            .email({tlds: {allow: env.ALLOW_TLDS_EMAIL_VALIDATION === "true"}})
            .min(4)
            .max(40),
        password: Joi.string().min(4).max(20).required(),
        captcha: Joi.string().required(),
    })
    .required();

Исправления конфигурации TypeScript

Убедитесь, что ваш файл tsconfig.json содержит следующие настройки:

json
{
  "compilerOptions": {
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "moduleResolution": "node",
    "resolveJsonModule": true
  }
}

Настройка esModuleInterop: true помогает с совместимостью импорта по умолчанию, в то время как allowSyntheticDefaultImports обеспечивает более гибкие шаблоны импорта.


Лучшие практики использования Joi

1. Последовательный метод импорта

Придерживайтесь одного метода импорта на протяжении всего проекта. Импорт по пространству имен (import * as Joi from "joi") обычно является наиболее надежным подходом.

2. Шаблон цепочки вызовов методов

Всегда используйте правильную цепочку вызовов методов и избегайте хранения промежуточных результатов:

typescript
// Хорошо
const schema = Joi.object({
    field: Joi.string().min(3).max(10).required()
});

// Плохо - может вызвать проблемы с контекстом
const string = Joi.string();
const schema = Joi.object({
    field: string.min(3).max(10).required()
});

3. Типобезопасные функции валидации

Создавайте типобезопасные функции валидации для раннего обнаружения ошибок:

typescript
import { ValidationOptions } from 'joi';

export function validate<T>(schema: Joi.Schema, data: unknown, options?: ValidationOptions): T {
    const { error, value } = schema.validate(data, options);
    if (error) {
        throw new Error(`Валидация не пройдена: ${error.message}`);
    }
    return value as T;
}

// Использование
const validatedData = validate<AuthenticateAccountPayload>(
    AUTHENTICATE_ACCOUNT_SCHEMA, 
    inputData
);

Руководство по миграции на Joi v18

Ключевые изменения в Joi v18

  • Изменения в методах импорта: Деструктурирующие импорты теперь требуют правильного контекста
  • Определения типов: Обновленные типы TypeScript с более строгой валидацией
  • Привязка методов: Методы должны вызываться на правильных экземплярах Joi

Пошаговая миграция

  1. Обновление операторов импорта:

    typescript
    // До (v17)
    import { object, string } from 'joi';
    
    // После (v18)
    import * as Joi from 'joi';
    
  2. Замена определений схем:

    typescript
    // До
    const schema = object({
        name: string().min(3)
    });
    
    // После
    const schema = Joi.object({
        name: Joi.string().min(3)
    });
    
  3. Обновление вызовов валидации:

    typescript
    // До
    const result = schema.validate(data);
    
    // После
    const result = Joi.validate(data, schema);
    

Расширенное устранение неполадок

1. Проблемы с расширениями Joi

Если вы используете расширения Joi, убедитесь, что они правильно импортированы и расширены:

typescript
import Joi from 'joi';
import customExtension from 'joi-custom-extension';

// Расширение Joi с пользовательскими расширениями
const extendedJoi = Joi.extend(customExtension);

// Использование расширенного Joi
const schema = extendedJoi.object({
    field: extendedJoi.customType()
});

2. Динамическое создание схем

Для динамических схем используйте Joi.alternatives() с правильной проверкой экземпляра:

typescript
const dynamicSchema = Joi.alternatives().try(
    Joi.object({ type: 'A', fieldA: Joi.string() }),
    Joi.object({ type: 'B', fieldB: Joi.number() })
).describe();

3. Пользовательские сообщения об ошибках валидации

Реализуйте пользовательскую обработку ошибок для предоставления лучшей информации для отладки:

typescript
const schema = Joi.object({
    email: Joi.string()
        .email()
        .error(errors => {
            return errors.map(error => {
                if (error.type === 'string.email') {
                    return new Error('Пожалуйста, укажите действительный адрес электронной почты');
                }
                return error;
            });
        })
});

Источники

  1. StackOverflow - AssertionError: you must pass Joi as an argument
  2. GitHub Issue #2295 - Must be invoked on a Joi instance
  3. GitHub Issue #1349 - Breaking changes in the latest versions
  4. GitHub Issue #1631 - Destructuring Throws Fatal
  5. StackOverflow - Joi getting validationSchema: Joi.object() error
  6. GitHub - Joi 18.0.0 Release Notes

Заключение

Ошибка “AssertError: Must be invoked on a Joi instance” в Joi v18.0.1 обычно решается путем использования импорта по пространству имен (import * as Joi from "joi") и обеспечения правильной цепочки вызовов методов. Ключевые выводы включают:

  1. Используйте импорт по пространству имен для поддержания правильного контекста для методов Joi
  2. Настройте TypeScript с esModuleInterop: true для лучшей совместимости импорта
  3. Избегайте деструктурирующих импортов, которые теряют контекст метода
  4. Реализуйте типобезопасные функции валидации для раннего обнаружения ошибок
  5. Следуйте последовательным шаблонам на протяжении всего проекта для предотвращения проблем

Если вы все еще сталкиваетесь с проблемами, рассмотрите возможность временного понижения версии до Joi v17.x или проверьте наличие любых пользовательских расширений Joi, которые могут вызывать конфликты. Миграция на Joi v18 требует внимания к шаблонам импорта и привязки методов, но улучшенная поддержка TypeScript и возможности валидации делают это worthwhile.

Авторы
Проверено модерацией
Модерация