ArkType для TypeScript: полное руководство
1. Что это и зачем нужна
ArkType — это современная библиотека для валидации данных в TypeScript, которая работает на уровне типов. В отличие от традиционных валидаторов (Zod, Yup), ArkType использует уникальный подход: он преобразует определения типов в оптимизированные парсеры и валидаторы на этапе компиляции. Это позволяет достичь высокой производительности и полной интеграции с системой типов TypeScript.
Зачем нужна ArkType? В современных веб-приложениях данные часто приходят из внешних источников: API, формы, localStorage, WebSocket. Без валидации вы рискуете получить ошибки времени выполнения, которые сложно отловить. ArkType решает эту проблему, предоставляя декларативный способ описания схем данных и автоматическую генерацию TypeScript-типов из этих схем. Библиотека особенно полезна при работе с REST API, GraphQL, формами и любыми внешними данными.
Ключевое преимущество ArkType — скорость. Благодаря статической компиляции схем, валидация выполняется в 10-100 раз быстрее, чем у аналогов. Кроме того, ArkType поддерживает рекурсивные типы, циклические ссылки, строгие проверки и множество встроенных типов. Это делает её идеальным выбором для высоконагруженных приложений и микросервисов.
2. Установка
Установка ArkType выполняется через пакетный менеджер npm (или yarn/pnpm). Библиотека написана на TypeScript и поставляется с полной поддержкой типов.
npm install arktype
Для работы с TypeScript дополнительных настроек не требуется — библиотека автоматически подхватывает конфигурацию tsconfig.json. Минимальная версия TypeScript — 5.0.
# Если используете yarn
yarn add arktype
# Если используете pnpm
pnpm add arktype
3. Быстрый старт
Минимальный пример использования ArkType — создание схемы для валидации объекта пользователя:
import { type } from 'arktype';
// Определяем схему
const User = type({
name: 'string',
age: 'number',
email: 'string.email'
});
// Валидируем данные
const result = User({
name: 'Иван',
age: 25,
email: 'ivan@example.com'
});
if (result instanceof type.errors) {
console.error('Ошибка валидации:', result.summary);
} else {
console.log('Данные валидны:', result);
// result автоматически имеет тип { name: string; age: number; email: string }
}
В этом примере мы создали схему User, которая проверяет три поля. Если данные не проходят валидацию, возвращается объект ошибки с подробным описанием. Если всё ок — возвращаются типизированные данные.
4. Основные методы и функции
ArkType предоставляет богатый API для создания схем, их комбинирования и трансформации. Рассмотрим 8 ключевых методов.
4.1. type() — создание схемы
Сигнатура: type(schema: TypeSchema): Type
Описание: Основной метод для создания схемы валидации. Принимает объект или строку с определением типа.
import { type } from 'arktype';
// Простая схема
const StringType = type('string');
const NumberType = type('number');
// Объектная схема
const Person = type({
name: 'string',
age: 'number',
isActive: 'boolean'
});
// Валидация
console.log(StringType('hello')); // 'hello'
console.log(StringType(123)); // Ошибка: Expected string, got number
4.2. .array() — массив элементов
Сигнатура: type.array(): Type
Описание: Создаёт схему для массива элементов указанного типа.
import { type } from 'arktype';
const Numbers = type('number').array();
const Users = type({
name: 'string',
age: 'number'
}).array();
// Валидация
console.log(Numbers([1, 2, 3])); // [1, 2, 3]
console.log(Numbers([1, '2', 3])); // Ошибка: Expected number at index 1
console.log(Users([{ name: 'Иван', age: 25 }])); // Массив пользователей
4.3. .optional() — опциональное поле
Сигнатура: type.optional(): Type
Описание: Делает поле необязательным. Если значение отсутствует или undefined, валидация проходит успешно.
import { type } from 'arktype';
const User = type({
name: 'string',
age: 'number.optional',
email: 'string.email.optional'
});
// Валидация
console.log(User({ name: 'Иван' })); // { name: 'Иван' }
console.log(User({ name: 'Иван', age: 25 })); // { name: 'Иван', age: 25 }
console.log(User({ name: 'Иван', email: 'не email' })); // Ошибка: Invalid email
4.4. .union() — объединение типов
Сигнатура: type.union(...types: Type[]): Type
Описание: Создаёт схему, которая принимает значение, соответствующее хотя бы одному из переданных типов.
import { type } from 'arktype';
const StringOrNumber = type('string').union(type('number'));
const Status = type('"active"').union(type('"inactive"')).union(type('"pending"'));
// Валидация
console.log(StringOrNumber('hello')); // 'hello'
console.log(StringOrNumber(42)); // 42
console.log(StringOrNumber(true)); // Ошибка: Expected string | number
console.log(Status('active')); // 'active'
console.log(Status('deleted')); // Ошибка: Expected "active" | "inactive" | "pending"
4.5. .intersection() — пересечение типов
Сигнатура: type.intersection(...types: Type[]): Type
Описание: Создаёт схему, которая проверяет, что значение соответствует всем переданным типам одновременно.
import { type } from 'arktype';
const HasName = type({ name: 'string' });
const HasAge = type({ age: 'number' });
const Person = HasName.intersection(HasAge);
// Валидация
console.log(Person({ name: 'Иван', age: 25 })); // { name: 'Иван', age: 25 }
console.log(Person({ name: 'Иван' })); // Ошибка: Missing required field 'age'
console.log(Person({ name: 'Иван', age: '25' })); // Ошибка: Expected number for 'age'
4.6. .pick() — выбор полей
Сигнатура: type.pick(...keys: string[]): Type
Описание: Создаёт новую схему, включающую только указанные поля из исходной объектной схемы.
import { type } from 'arktype';
const FullUser = type({
id: 'number',
name: 'string',
email: 'string.email',
password: 'string'
});
const PublicUser = FullUser.pick('id', 'name', 'email');
// Валидация
console.log(PublicUser({
id: 1,
name: 'Иван',
email: 'ivan@example.com'
})); // { id: 1, name: 'Иван', email: 'ivan@example.com' }
console.log(PublicUser({
id: 1,
name: 'Иван',
email: 'ivan@example.com',
password: 'secret'
})); // Ошибка: Unexpected field 'password'
4.7. .omit() — исключение полей
Сигнатура: type.omit(...keys: string[]): Type
Описание: Создаёт новую схему, исключающую указанные поля из исходной объектной схемы.
import { type } from 'arktype';
const FullUser = type({
id: 'number',
name: 'string',
email: 'string.email',
password: 'string'
});
const SafeUser = FullUser.omit('password');
// Валидация
console.log(SafeUser({
id: 1,
name: 'Иван',
email: 'ivan@example.com'
})); // { id: 1, name: 'Иван', email: 'ivan@example.com' }
console.log(SafeUser({
id: 1,
name: 'Иван',
email: 'ivan@example.com',
password: 'secret'
})); // Ошибка: Unexpected field 'password'
4.8. .pipe() — трансформация данных
Сигнатура: type.pipe(transform: (value: T) => U): Type
Описание: Позволяет применить функцию трансформации к валидированным данным. Полезно для преобразования типов или форматирования.
import { type } from 'arktype';
const StringToNumber = type('string').pipe((s: string) => parseInt(s, 10));
const TrimmedString = type('string').pipe((s: string) => s.trim());
// Валидация с трансформацией
console.log(StringToNumber('42')); // 42 (число)
console.log(StringToNumber('не число')); // NaN (но валидация пройдена)
console.log(TrimmedString(' hello ')); // 'hello' (обрезано)
// Комбинирование с валидацией
const PositiveInt = type('string')
.pipe((s: string) => parseInt(s, 10))
.pipe((n: number) => {
if (n <= 0) throw new Error('Число должно быть положительным');
return n;
});
console.log(PositiveInt('5')); // 5
console.log(PositiveInt('-1')); // Ошибка: Число должно быть положительным
5. Реальный практический пример
Рассмотрим комплексный пример — создание системы валидации для REST API с обработкой ошибок и трансформацией данных.
import { type } from 'arktype';
// 1. Определяем схемы для различных сущностей
const Address = type({
street: 'string',
city: 'string',
zipCode: 'string.pipe((s: string) => s.replace(/\s/g, ""))', // Удаляем пробелы
country: 'string'
});
const UserCreate = type({
name: 'string.min(2).max(50)',
email: 'string.email',
age: 'number.integer.min(0).max(150)',
address: Address.optional(),
roles: type('"admin"').union(type('"user"')).union(type('"moderator"')).array()
});
const UserUpdate = UserCreate.partial(); // Все поля опциональны
// 2. Создаём класс для работы с пользователями
class UserService {
async createUser(data: unknown) {
const result = UserCreate(data);
if (result instanceof type.errors) {
return {
success: false,
errors: result.summary.map(err => ({
path: err.path,
message: err.message,
code: err.code
}))
};
}
// Трансформируем данные перед сохранением
const user = {
...result,
createdAt: new Date(),
updatedAt: new Date(),
isActive: true
};
// Сохраняем в базу данных (имитация)
console.log('Сохранён пользователь:', user);
return { success: true, data: user };
}
async updateUser(id: number, data: unknown) {
const result = UserUpdate(data);
if (result instanceof type.errors) {
return {
success: false,
errors: result.summary.map(err => ({
path: err.path,
message: err.message,
code: err.code
}))
};
}
// Обновляем пользователя (имитация)
console.log(`Обновлён пользователь ${id}:`, result);
return { success: true, data: result };
}
async validateBulk(users: unknown[]) {
const UsersArray = UserCreate.array();
const result = UsersArray(users);
if (result instanceof type.errors) {
return {
success: false,
errors: result.summary.map(err => ({
index: err.path?.[0],
message: err.message
}))
};
}
return { success: true, data: result };
}
}
// 3. Пример использования
async function main() {
const service = new UserService();
// Успешное создание
const createResult = await service.createUser({
name: 'Иван Петров',
email: 'ivan@example.com',
age: 30,
address: {
street: 'ул. Ленина, 10',
city: 'Москва',
zipCode: '101000',
country: 'Россия'
},
roles: ['admin', 'user']
});
console.log('Результат создания:', createResult);
// Ошибка валидации
const errorResult = await service.createUser({
name: 'А',
email: 'не email',
age: -5,
roles: ['superadmin']
});
console.log('Ошибки:', errorResult);
// Валидация массива пользователей
const bulkResult = await service.validateBulk([
{ name: 'Пользователь 1', email: 'user1@test.com', age: 25, roles: ['user'] },
{ name: 'Пользователь 2', email: 'user2@test.com', age: 30, roles: ['user'] }
]);
console.log('Массовая валидация:', bulkResult);
}
main().catch(console.error);
Этот пример демонстрирует реальное использование ArkType в серверном приложении: создание сложных схем с вложенными объектами, массивами, трансформациями, обработку ошибок и массовую валидацию.