Validate on submit
Composes: parsePhoneNumber + PhoneNumber + ValidationError
Accept or reject a phone field when a form is submitted, and store the accepted value. On submit, the field’s digits drive one deterministic automaton compiled from Google’s metadata; validity is an accepting state, and every ValidationError variant is a named way of not being in one (the model).
import { ensureEngineReady, parsePhoneNumber, type ValidationError } from '@telixon/core';
await ensureEngineReady();
type SubmitOutcome = | { readonly accepted: true; readonly e164: string } | { readonly accepted: false; readonly error: ValidationError | null };
function validatePhoneField(raw: string): SubmitOutcome { const phoneNumber = parsePhoneNumber(raw, { defaultRegion: 'US' }); const e164 = phoneNumber.formatE164(); if (phoneNumber.isValid() && e164 !== null) { return { accepted: true, e164 }; } return { accepted: false, error: phoneNumber.getValidationError() };}
validatePhoneField('+1 415 555 0132');// -> { accepted: true, e164: '+14155550132' }
validatePhoneField('(415) 555-0132');// -> { accepted: true, e164: '+14155550132' }
validatePhoneField('415 555');// -> { accepted: false, error: { kind: 'TOO_SHORT', minLength: 10 } }Submitting +1 415 555 0132 or (415) 555-0132 stores the same string, +14155550132. Submitting 415 555 is rejected with a TOO_SHORT error carrying the minimum national length, 10.
1. Gate on engine readiness
Section titled “1. Gate on engine readiness”Await ensureEngineReady once before the first parse; parsePhoneNumber throws EngineNotReadyError if called earlier. Where to place this call in an application is covered in Engine loading.
2. Parse the raw field value
Section titled “2. Parse the raw field value”Pass the value exactly as the user typed it; (415) 555-0132 resolves without stripping punctuation. Set defaultRegion to the region the form expects for national input; input with a leading + carries its own calling code and ignores defaultRegion.
3. Branch on isValid()
Section titled “3. Branch on isValid()”Accept when isValid() returns true, reject otherwise; do not branch on getValidationError() === null (see step 5). By default isValid() accepts a number that is valid for any region the digits can resolve to. To accept only numbers of the defaultRegion, pass strict: true; see Strict mode.
4. Store formatE164()
Section titled “4. Store formatE164()”E.164 is one region-independent string per number, so stored values compare by equality and reparse without a defaultRegion. formatE164() is non-null whenever isValid() returns true; the e164 !== null check in the handler narrows the type without an assertion. The other formatters on PhoneNumber are for display, not storage.
5. Accept NATIONAL_PREFIX_MISSING
Section titled “5. Accept NATIONAL_PREFIX_MISSING”NATIONAL_PREFIX_MISSING is the one ValidationError variant that coexists with isValid() returning true: the digits reach an accepting state, and the variant records that the region’s national prefix was absent from the input.
const phoneNumber = parsePhoneNumber('7400 123456', { defaultRegion: 'GB' });
phoneNumber.isValid();// -> truephoneNumber.getValidationError();// -> { kind: 'NATIONAL_PREFIX_MISSING', expectedPrefix: '0' }phoneNumber.formatE164();// -> '+447400123456'phoneNumber.formatNational();// -> '07400 123456'Because the handler branches on isValid() first, this submission is accepted and +447400123456 is stored. Optionally echo formatNational() back into the field so the user sees the national form with the prefix restored: 07400 123456.
6. Surface the error when rejected
Section titled “6. Surface the error when rejected”When isValid() returns false, getValidationError() names the reason as a discriminated union on kind, with variant-specific data such as minLength above. The full union is specified on the ValidationError page; mapping each variant to a field message is covered in Form errors.
Next steps
Section titled “Next steps”- Form errors: turn each
ValidationErrorvariant into a field message. - Drive an input: validate while the user types;
parsePhoneNumberand the input controller are the same shared resolve layer, queried in batch versus per keystroke. - Strict mode: restrict validity to a single region.