Masks & validation
A mask is about how a value looks, validation is about whether it's correct. A mask won't let a letter into a phone number, but it doesn't know the number is unfinished or that a tax ID checksum is wrong.
Check that the mask is filled
isComplete — all required positions are filled. isValid — filled and correct (what exactly is checked — in Ready-made masks):
ts
isComplete('+7 (999) 123-45', phone()); // false
isComplete('+7 (999) 123-45-67', phone()); // true
// filled and correct
isValid('31.02.2026', date()); // false — no such date
isValid('4111 1111 1111 1112', card()); // false — wrong check digitWith a tinyval schema
Remove the mask in preprocess — then the clean value is validated and goes to data:
ts
import { validate, object, string, pipe, preprocess, length, pattern, coerce } from 'tinyval';
import { unmask, isComplete } from 'tinyval-mask';
import { phone, card, money } from 'tinyval-mask/presets';
const phoneMask = phone();
const cardMask = card();
const moneyMask = money();
// the mask is removed first, then the clean value is validated
const unmasked = (m: Parameters<typeof unmask>[1]) => (v: unknown) => (v === '' || v == null ? v : unmask(String(v), m));
const Order = object({
phone: preprocess(unmasked(phoneMask), pipe(string(), length(11))),
card: preprocess(unmasked(cardMask), pipe(string(), pattern(/^\d{16,19}$/))),
amount: preprocess(unmasked(moneyMask), coerce.number()),
});
validate(Order, { phone: '+7 (999) 123-45-67', card: '4276 1234 5678 9012', amount: '1 500,50' }).data;
// { phone: '79991234567', card: '4276123456789012', amount: 1500.5 }- An empty value stays empty — the empty values rule and
optionalwork. - The same works on the server: masks are pure functions, so
unmasknormalizes phones sent in any format.