Migrate from Google libphonenumber
Composes: ensureEngineReady + parsePhoneNumber + createInternationalInputController
This guide maps each Google libphonenumber method to its @telixon/core equivalent and lists the behavioral differences that matter while porting. Telixon is built on the same foundation: digits drive one deterministic automaton compiled from Google’s metadata, so a migration changes the API surface, not the answers.
The before code uses the google-libphonenumber npm package, a third-party wrapper of Google’s JavaScript implementation.
// Before: google-libphonenumberconst { PhoneNumberUtil, PhoneNumberFormat } = require('google-libphonenumber');
const phoneUtil = PhoneNumberUtil.getInstance();
const parsed = phoneUtil.parseAndKeepRawInput('415 555 0132', 'US');phoneUtil.isValidNumber(parsed); // truephoneUtil.format(parsed, PhoneNumberFormat.E164); // '+14155550132'phoneUtil.format(parsed, PhoneNumberFormat.NATIONAL); // '(415) 555-0132'
phoneUtil.parse('+999 123 4567', 'US');// throws Error: Invalid country calling code// After: @telixon/coreimport { ensureEngineReady, parsePhoneNumber } from '@telixon/core';
await ensureEngineReady(); // once per process
const phoneNumber = parsePhoneNumber('415 555 0132', { defaultRegion: 'US' });phoneNumber.isValid(); // truephoneNumber.formatE164(); // '+14155550132'phoneNumber.formatNational(); // '(415) 555-0132'
parsePhoneNumber('+999 123 4567').getValidationError();// { kind: 'INVALID_CALLING_CODE' }Both programs report the same validity and the same formatted strings. That holds for every input, not only this one: Telixon has 100% geographic parity with Google libphonenumber, verified by exhaustive enumeration (Verified against Google libphonenumber).
API mapping
Section titled “API mapping”| Google libphonenumber | @telixon/core | Notes |
|---|---|---|
parse(input, region), parseAndKeepRawInput(input, region) |
parsePhoneNumber(input, { defaultRegion }) |
One function covers both call forms; the region argument becomes the defaultRegion option. Never throws for bad input. |
isValidNumber(number) |
phoneNumber.isValid() |
|
isValidNumberForRegion(number, region) |
phoneNumber.isValidForRegion(region) or strict mode |
isValidForRegion for a one-off check; strict: true at parse time to restrict the whole instance. |
isPossibleNumber(number) |
phoneNumber.isPossible() |
|
isPossibleNumberWithReason(number) |
phoneNumber.isPossibleWithReason() |
Same reason vocabulary, returned as string literals instead of a numeric enum; Google’s INVALID_COUNTRY_CODE is named INVALID_CALLING_CODE. See PossibilityResult. |
getNumberType(number) |
phoneNumber.getNumberType() |
Returns string literals such as 'MOBILE', not a numeric enum. |
getRegionCodeForNumber(number) |
phoneNumber.getRegion() |
|
format(number, PhoneNumberFormat.E164) |
phoneNumber.formatE164() |
One method per format; each returns string | null. |
format(number, PhoneNumberFormat.NATIONAL) |
phoneNumber.formatNational() |
|
format(number, PhoneNumberFormat.INTERNATIONAL) |
phoneNumber.formatInternational() |
|
format(number, PhoneNumberFormat.RFC3966) |
phoneNumber.formatRfc3966() |
|
getExampleNumber(region), getExampleNumberForType(region, type) |
getPlaceholders(region, type) |
Returns preformatted national, nationalWithPrefix, and international strings instead of a number object. |
getCountryCodeForRegion(region) |
getCallingCodeForRegion(region) |
Returns the calling code as a string ('44'), not a number. |
AsYouTypeFormatter |
The input controller | A full input controller with caret positions, undo/redo history, and mid-typing number queries, not a formatter. |
Differences
Section titled “Differences”- The engine ships precompiled. The wrapper bundles Google’s metadata and parses it at runtime. Telixon compiles the same metadata into one deterministic automaton at build time and loads it as a binary artifact, so no metadata is parsed at runtime. Mechanics are on Engine.
- Initialization is one await up front.
PhoneNumberUtil.getInstance()is synchronous because the metadata travels in the bundle. Telixon loads the engine when you callensureEngineReady, once per process; before it resolves, every query throwsEngineNotReadyError, and after it, every call in this guide is synchronous. - Bad input is a value, not an exception.
parsethrowsNumberParseException(a thrownErrorin the npm wrapper).parsePhoneNumbernever throws for bad input: every input resolves to aPhoneNumber, andgetValidationError()names the failure as a discriminated union. Validity is an accepting state, and everyValidationErrorvariant is a named way of not being in one.
1. Initialize once with one await
Section titled “1. Initialize once with one await”Replace PhoneNumberUtil.getInstance() with one await ensureEngineReady() at process startup.
import { ensureEngineReady } from '@telixon/core';
await ensureEngineReady();Per-environment entry points and loading strategies, including the synchronous sync-init entry, are covered in Engine loading.
2. Replace parse and delete the try/catch
Section titled “2. Replace parse and delete the try/catch”Ported parse sites branch on the returned value instead of catching.
import { parsePhoneNumber } from '@telixon/core';
const phoneNumber = parsePhoneNumber('415 555', { defaultRegion: 'US' });
if (!phoneNumber.isValid()) { phoneNumber.getValidationError(); // { kind: 'TOO_SHORT', minLength: 10 } return;}Variants carry the data a message needs (minLength, maxLength, possibleLengths, expectedPrefix); the full union is on ValidationError, and mapping each variant to user-facing copy is Show form errors.
3. Move each query onto the number
Section titled “3. Move each query onto the number”Google’s queries are methods on PhoneNumberUtil that take the parsed number back; Telixon’s are methods on the returned PhoneNumber. Rewrite each call site with the API mapping table. The one decision point is isValidNumberForRegion, which has two ports:
// One-off check: query the region directly.const phoneNumber = parsePhoneNumber('+1 415 555 0132');phoneNumber.isValidForRegion('US'); // truephoneNumber.isValidForRegion('GB'); // false
// Whole-instance restriction: parse in strict mode.const restricted = parsePhoneNumber('+1 212 555 0198', { defaultRegion: 'CA', strict: true,});restricted.isValid(); // false, valid in US but not in CAWhen to prefer which, and exactly what strict does and does not restrict, is covered in Strict mode.
4. Replace AsYouTypeFormatter with an input controller
Section titled “4. Replace AsYouTypeFormatter with an input controller”AsYouTypeFormatter accepts one digit through inputDigit and returns a formatted string; caret placement, deletion, and mid-string edits are left to the caller. The input controller consumes editing intents (insert, deleteBackward, setValue) and returns the next value with caret positions, keeps undo/redo history, and answers number queries mid-typing. parsePhoneNumber and the input controller are the same shared resolve layer, queried in batch versus per keystroke.
import { createInternationalInputController } from '@telixon/core';
const controller = createInternationalInputController({ display: { callingCodeInInput: true, plusPrefix: 'fixed' },});
let state = controller.currentState; // value: '+'for (const digit of '442079') { state = controller.insert(state.value, digit, state.selectionStart, state.selectionEnd);}
state.value; // '+44 20 79'state.region; // 'GB'controller.getPhoneNumber().isValid(); // false, the number is still incompleteFor a single-country field, use createNationalInputController instead. Wiring a controller into a real text field is Drive an input; for a prebuilt DOM binding, see @telixon/web-sdk.
Next steps
Section titled “Next steps”- Validate on submit for the submit-time flow your ported parse calls feed
- Show form errors to turn every
ValidationErrorvariant into a message - Verified against Google libphonenumber for how parity is enforced in CI