From 9c984b137f4122191b83fc30e9a2091f46ac3bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 10 Feb 2026 20:56:43 +0100 Subject: [PATCH] Fix phone validation performance by using Set/Map instead of Array lookups (#17843) ## Summary - **`isValidCountryCode`** was using `Array.includes()` on ~250 country codes (O(n) per call). Replaced with a `Set.has()` lookup (O(1)). - **`getCountryCodesForCallingCode`** was iterating all ~250 countries and calling `getCountryCallingCode()` on each one **every invocation**. Replaced with a precomputed `Map` built once at module load (O(1) per call). Both functions are called from `transformPhonesValue` on every phone field mutation, causing cumulative overhead visible in profiling (p95 self-time ~20-35ms). ## Test plan - [ ] Verify phone field creation/update still works correctly (country code validation, calling code resolution) - [ ] Verify spreadsheet import with phone fields still validates properly - [ ] Confirm no regression in `isValidCountryCode` or `getCountryCodesForCallingCode` behavior Made with [Cursor](https://cursor.com) Co-authored-by: Cursor --- .../getCountryCodesForCallingCode.ts | 31 ++++++++++++++----- .../phones-value/isValidCountryCode.ts | 4 +-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/twenty-shared/src/utils/validation/phones-value/getCountryCodesForCallingCode.ts b/packages/twenty-shared/src/utils/validation/phones-value/getCountryCodesForCallingCode.ts index be7a21487e4..de2fc1f085e 100644 --- a/packages/twenty-shared/src/utils/validation/phones-value/getCountryCodesForCallingCode.ts +++ b/packages/twenty-shared/src/utils/validation/phones-value/getCountryCodesForCallingCode.ts @@ -1,15 +1,30 @@ -import { getCountries, getCountryCallingCode } from 'libphonenumber-js'; +import { + type CountryCode, + getCountries, + getCountryCallingCode, +} from 'libphonenumber-js'; -const ALL_COUNTRIES_CODE = getCountries(); +// Precompute a map from calling code to country codes for O(1) lookups +const CALLING_CODE_TO_COUNTRIES = new Map(); -export const getCountryCodesForCallingCode = (callingCode: string) => { +for (const country of getCountries()) { + const callingCode = getCountryCallingCode(country); + + const existing = CALLING_CODE_TO_COUNTRIES.get(callingCode); + + if (existing) { + existing.push(country); + } else { + CALLING_CODE_TO_COUNTRIES.set(callingCode, [country]); + } +} + +export const getCountryCodesForCallingCode = ( + callingCode: string, +): CountryCode[] => { const cleanCallingCode = callingCode.startsWith('+') ? callingCode.slice(1) : callingCode; - return ALL_COUNTRIES_CODE.filter((country) => { - const countryCallingCode = getCountryCallingCode(country); - - return countryCallingCode === cleanCallingCode; - }); + return CALLING_CODE_TO_COUNTRIES.get(cleanCallingCode) ?? []; }; diff --git a/packages/twenty-shared/src/utils/validation/phones-value/isValidCountryCode.ts b/packages/twenty-shared/src/utils/validation/phones-value/isValidCountryCode.ts index 6df428e2a74..ff48a7c0d4e 100644 --- a/packages/twenty-shared/src/utils/validation/phones-value/isValidCountryCode.ts +++ b/packages/twenty-shared/src/utils/validation/phones-value/isValidCountryCode.ts @@ -1,7 +1,7 @@ import { type CountryCode, getCountries } from 'libphonenumber-js'; -const ALL_COUNTRIES_CODE = getCountries(); +const ALL_COUNTRIES_CODE_SET = new Set(getCountries()); export const isValidCountryCode = (input: string): input is CountryCode => { - return ALL_COUNTRIES_CODE.includes(input as unknown as CountryCode); + return ALL_COUNTRIES_CODE_SET.has(input); };