@mineflow/shared-validation
Frontend-safe проверки бизнес-форматов Республики Казахстан: контрольные суммы ИИН, БИН и mod-97 для KZ IBAN. Чистые функции без зависимо стей — работают одинаково в браузере и React Native.
Что это и зачем
Пакет относится к слою L0 SDK (ADR-0042) — тот же уровень, что и @mineflow/api-zod. Но если api-zod codegen'ится из OpenAPI и проверяет структуру формы (типы, длины, обязательность), то checksum-проверки в схему не попадают: OpenAPI описывает iin как string длиной 12, но не знает алгоритм контрольной цифры НК РК. shared-validation закрывает именно этот зазор — семантическую корректность идентификаторов, которую нельзя выразить в JSON Schema.
Функции выделены в отдельный пакет, потому что один и тот же ИИН/БИН валидируется в нескольких местах: scm/suppliers (БИН организации, ИИН ИП), hr/personnel (ИИН сотрудника) и любых будущих формах. Это закрывает open-question Q17 модуля scm/suppliers (ADR-0028). Пакет zero-runtime-dependency и sideEffects: false — tree-shaking забирает только реально вызванные функции.
Установка
pnpm add @mineflow/shared-validation
У пакета нет peer- и runtime-зависимостей. mod-97 для IBAN считается итеративно, без BigInt, чтобы оставаться совместимым с любым target и любой средой исполнения. В монорепо MineFlow резолвится как workspace:*.
Использование
Все три функции принимают string и возвращают boolean. Они не бросают исключений и сами защищаются от не-строк, неверной длины и посторонних символов — невалидный вход просто даёт false.
import {
validBinChecksum,
validIbanMod97,
validIinChecksum,
} from '@mineflow/shared-validation';
validIinChecksum('900101350510'); // true | false по контрольной цифре
validBinChecksum('123456789012'); // true | false
validIbanMod97('KZ75125KZT2069100100'); // true | false (20 символов, начинается с 'KZ')
В Zod-форме через .refine()
Главный сценарий — дополнить структурную схему из api-zod checksum-проверкой, которой там нет. Накладываем .refine() поверх готового поля:
import { z } from 'zod';
import { validBinChecksum, validIinChecksum } from '@mineflow/shared-validation';
const supplierForm = z.object({
name: z.string().min(1),
// структуру (12 цифр) проверяет regex, контрольную сумму — refine
bin: z
.string()
.regex(/^\d{12}$/, 'БИН — это 12 цифр')
.refine(validBinChecksum, { message: 'Неверная контрольная сумма БИН' }),
iin: z
.string()
.regex(/^\d{12}$/, 'ИИН — это 12 цифр')
.refine(validIinChecksum, { message: 'Неверная контрольная сумма ИИН' }),
iban: z
.string()
.refine(validIbanMod97, { message: 'Неверный IBAN (mod-97)' }),
});
Держи .regex()/структуру до .refine(): тогда пользователь сперва увидит понятное «12 цифр», а сообщение о контрольной сумме — только когда формат уже верный. Так как функции возвращают false на любой мусор, порядок ещё и страхует от шумных сообщений на полупустом поле.
Если работаешь с codegen-схемой из api-zod напрямую, расширяй её тем же приёмом — zCreateSupplierDto.refine(...) или через .extend({ bin: ... }). Подключение схемы к react-hook-form через zodResolver и обработку ошибок полей смотри в рецепте Формы.
Ключевые экспорты
| Экспорт | Сигнатура | Что проверяет |
|---|---|---|
validIinChecksum | (iin: string) => boolean | ИИН РК: 12 цифр, контрольная цифра по алгоритму НК РК (веса w1, при результате 10 — пересчёт по w2) |
validBinChecksum | (bin: string) => boolean | БИН РК: тот же алгоритм, что и ИИН (12 цифр, w1 → при 10 пересчёт по w2) |
validIbanMod97 | (iban: string) => boolean | KZ IBAN: ровно 20 символов, начинается с KZ, проверка ISO 13616 mod-97 (остаток должен быть 1) |
Полный перечень и сигнатуры — в API-референсе из кода.
Поведение валидаторов
validIinChecksum/validBinChecksum— алгоритм идентичен (разнесены лишь для семантической чистоты: ИИН — физлицо/ИП, БИН — организация). Считаютsum(d_i * w1_i) mod 11по первым 11 цифрам; если результат< 10— это контрольная цифра. Если результат== 10, пересчёт по второму набору весов w2; если и он даёт 10 — идентификатор невалиден.validIbanMod97— заточен под KZ: длина строго 20 и префиксKZзашиты в реализацию. Переставляет первые 4 символа в конец, заменяет буквы числами (A=10 … Z=35) и считает mod-97 итеративно. Валиден, когда остаток равен 1.
Подводные камни
Валидаторы не очищают строку: пробелы, дефисы и нижний регистр приводят к false.
- IBAN из UI обычно приходит как
KZ75 125K ZT20 6910 0100— перед проверкой убери пробелы и приведи к верхнему регистру:iban.replace(/\s/g, '').toUpperCase(). - ИИН/БИН — убери всё, кроме цифр, если в поле возможны разделители.
Делай нормализацию в Zod через z.string().transform(...) до .refine(...), чтобы валидатор всегда получал чистое значение.
validIbanMod97 хардкодит длину 20 и префикс KZ — это не общий валидатор IBAN произвольной страны. Любой не-KZ IBAN вернёт false. Для MineFlow это by design: контрагенты — резиденты РК.
Checksum-проверки на фронте — UX-слой (мгновенная подсказка в форме). Backend (scm/suppliers, hr/personnel) валидирует те же инварианты независимо (см. SCM-SUP-INV-06). Frontend-проверка не отменяет серверную и не является источником истины.
React Native
Пакет полностью frontend-safe: чистые функции, нет обращений к window/document, нет BigInt, нет нативных модулей. Импорт и вызовы в RN идентичны вебу:
import { validIinChecksum } from '@mineflow/shared-validation';
Подробнее об ограничениях SDK в RN-окружении — рецепт React Native.
Ссылки
- Полный API-референс из кода → /api/shared-validation/
- Структурные схемы форм →
@mineflow/api-zod - Подключение схем к формам и
react-hook-form→ реце пт Формы - Обзор слоёв SDK → ADR-0042