nig-utils
Version:
A fully-typed, production-grade utility library for Nigerian developers
779 lines (774 loc) • 25.1 kB
text/typescript
/**
* Type definitions for Nigerian telecommunications utilities
*
* @fileoverview Core types and interfaces for phone number handling
* @author Ademuyiwa Johnson
* @license MIT
*/
/**
* Nigerian telecommunications provider types
* Represents the four major telco operators in Nigeria
*
* @public
*/
type TelcoProvider = 'MTN' | 'GLO' | 'AIRTEL' | '9MOBILE';
/**
* Phone number format options for input and output formatting
*
* @public
* @example
* - `local`: '08031234567' (Nigerian local format)
* - `international`: '+2348031234567' (International format)
* - `e164`: '+2348031234567' (E.164 standard format)
*/
type PhoneFormat = 'local' | 'international' | 'e164';
/**
* Template literal type for local Nigerian phone numbers starting with '0'
* @internal
*/
type LocalPrefix = `0${string}`;
/**
* Template literal type for international Nigerian phone numbers starting with '+234'
* @internal
*/
type InternationalPrefix = `+234${string}`;
/**
* Template literal type for E.164 format Nigerian phone numbers
* @internal
*/
type E164Format = `+234${string}`;
/**
* Branded type for validated phone numbers to ensure type safety
* Prevents accidental use of unvalidated strings as phone numbers
*
* @public
*/
type ValidatedPhone = string & {
readonly __brand: 'ValidatedPhone';
};
/**
* Branded type for normalized phone numbers in E.164 format
* Ensures phone numbers have been processed through normalization
*
* @public
*/
type NormalizedPhone = string & {
readonly __brand: 'NormalizedPhone';
};
/**
* Comprehensive phone number information with validation results
* Contains all relevant data about a Nigerian phone number
*
* @public
* @interface PhoneInfo
* @example
* ```typescript
* const info = getPhoneInfo('08031234567');
* console.log(info.telco); // 'MTN'
* console.log(info.normalized); // '+2348031234567'
* ```
*/
interface PhoneInfo {
/** Original input phone number as provided */
readonly original: string;
/** Normalized phone number in E164 format (+234...) */
readonly normalized: NormalizedPhone;
/** Detected telecommunications provider or null if unknown */
readonly telco: TelcoProvider | null;
/** Whether the phone number passed validation */
readonly isValid: boolean;
/** Format of the original input (local, international, or e164) */
readonly format: PhoneFormat;
/** Telco prefix extracted from the number (e.g., '0803') */
readonly prefix: string;
/** Phone number without country code prefix */
readonly number: string;
}
/**
* Telecommunications provider information and metadata
* Contains prefixes and details for each Nigerian telco operator
*
* @public
* @interface TelcoPrefix
* @example
* ```typescript
* const mtnInfo = getTelcoInfo('MTN');
* console.log(mtnInfo.prefixes); // ['0703', '0803', ...]
* ```
*/
interface TelcoPrefix {
/** Provider name identifier */
readonly provider: TelcoProvider;
/** List of phone number prefixes for this provider */
readonly prefixes: readonly string[];
/** Human-readable provider description */
readonly description: string;
}
/**
* Result type for operations that may fail
* Provides type-safe error handling without exceptions
*
* @public
* @template T - The type of successful result data
* @example
* ```typescript
* const result = safeNormalizePhone('invalid');
* if (result.success) {
* console.log('Normalized:', result.data);
* } else {
* console.error('Error:', result.error);
* }
* ```
*/
type PhoneResult<T> = {
success: true;
data: T;
error: null;
} | {
success: false;
data: null;
error: string;
};
/**
* Performance statistics interface
* Contains metrics for monitoring library performance
*
* @public
* @interface PerformanceStats
*/
interface PerformanceStats {
/** Number of entries in phone info cache */
readonly cacheSize: number;
/** Number of cached regex patterns */
readonly regexCacheSize: number;
/** Number of prefix mappings */
readonly prefixMapSize: number;
}
/**
* Benchmark result interface
* Contains timing information for performance measurements
*
* @public
* @interface BenchmarkResult
*/
interface BenchmarkResult<T> {
/** The result of the benchmarked function */
readonly result: T;
/** Execution time in milliseconds */
readonly duration: number;
}
/**
* Batch benchmark result interface
* Contains results and timing for batch operations
*
* @public
* @interface BatchBenchmarkResult
*/
interface BatchBenchmarkResult<T> {
/** Array of results from batch operation */
readonly results: T[];
/** Average execution time per operation in milliseconds */
readonly avgDuration: number;
}
/**
* Phone parts interface
* Contains components of a split phone number
*
* @public
* @interface PhoneParts
*/
interface PhoneParts {
/** Telco prefix (e.g., '0803') */
readonly prefix: string;
/** Detected telco provider or null */
readonly telco: TelcoProvider | null;
/** Phone number without country code */
readonly number: string;
}
/**
* Nigerian Phone Number Library - Ultra Optimized Edition
* Performance-first implementation with advanced TypeScript features
*
* @fileoverview Main entry point for Nigerian phone number utilities
* @author Ademuyiwa Johnson
* @license MIT
*/
/**
* Custom error class for phone validation errors
* Provides error code and input context for better debugging
*
* @public
* @class PhoneValidationError
* @extends Error
* @example
* throw new PhoneValidationError('Invalid format', 'INVALID_FORMAT', '0803...');
*/
declare class PhoneValidationError extends Error {
/** Error code for programmatic handling */
readonly code: 'INVALID_FORMAT' | 'INVALID_LENGTH' | 'INVALID_COUNTRY_CODE';
/** The input that caused the error */
readonly input: string;
constructor(message: string, code: 'INVALID_FORMAT' | 'INVALID_LENGTH' | 'INVALID_COUNTRY_CODE', input: string);
}
/**
* Type guards with brand checking
*/
/**
* Type guard to check if a string is a validated phone number
* Uses brand checking for compile-time type safety
*
* @public
* @param phone - The phone number string to validate
* @returns True if the phone number is validated, false otherwise
* @example
* ```typescript
* if (isValidatedPhone(someString)) {
* // TypeScript knows someString is ValidatedPhone
* console.log('Valid phone:', someString);
* }
* ```
*/
declare const isValidatedPhone: (phone: string) => phone is ValidatedPhone;
/**
* Type guard to check if a string is a normalized phone number
* Ensures the phone number is in proper E.164 format
*
* @public
* @param phone - The phone number string to check
* @returns True if the phone number is normalized, false otherwise
* @example
* ```typescript
* if (isNormalizedPhone(phoneString)) {
* // TypeScript knows phoneString is NormalizedPhone
* sendSMS(phoneString); // Safe to use
* }
* ```
*/
declare const isNormalizedPhone: (phone: string) => phone is NormalizedPhone;
/**
* Utility functions with performance optimizations
* Core helper functions for telco and formatting operations
*/
/**
* Returns all available Nigerian telecommunications providers
*
* @public
* @returns Readonly array of all telco provider names
*
* @example
* ```typescript
* const providers = getAllTelcos();
* console.log(providers); // ['MTN', 'GLO', 'AIRTEL', '9MOBILE']
*
* // Use in dropdown/select options
* const options = getAllTelcos().map(telco => ({
* value: telco,
* label: getTelcoInfo(telco).description
* }));
*
* // Iterate through all providers
* getAllTelcos().forEach(telco => {
* const info = getTelcoInfo(telco);
* console.log(`${telco}: ${info.prefixes.length} prefixes`);
* });
* ```
*/
declare const getAllTelcos: () => readonly TelcoProvider[];
/**
* Gets detailed information about a specific telco provider
*
* @public
* @param provider - The telco provider to get information for
* @returns Provider information including prefixes and description
*
* @example
* ```typescript
* const mtnInfo = getTelcoInfo('MTN');
* console.log(mtnInfo);
* // {
* // provider: 'MTN',
* // prefixes: ['0703', '0704', '0706', '07025', '07026', ...],
* // description: 'MTN Nigeria'
* // }
*
* console.log(`MTN has ${mtnInfo.prefixes.length} prefixes`); // 15
*
* // Generate documentation
* getAllTelcos().forEach(telco => {
* const info = getTelcoInfo(telco);
* console.log(`${info.description}: ${info.prefixes.join(', ')}`);
* });
* ```
*/
declare const getTelcoInfo: (provider: TelcoProvider) => {
readonly provider: "MTN";
readonly prefixes: readonly ["0703", "0704", "0706", "07025", "07026", "0803", "0806", "0810", "0813", "0814", "0816", "0903", "0906", "0913", "0916"];
readonly description: "MTN Nigeria";
} | {
readonly provider: "GLO";
readonly prefixes: readonly ["0705", "0805", "0807", "0811", "0815", "0905", "0915"];
readonly description: "Globacom Nigeria";
} | {
readonly provider: "AIRTEL";
readonly prefixes: readonly ["0701", "0708", "0802", "0808", "0812", "0901", "0902", "0907", "0912"];
readonly description: "Airtel Nigeria";
} | {
readonly provider: "9MOBILE";
readonly prefixes: readonly ["0809", "0817", "0818", "0908", "0909"];
readonly description: "9mobile Nigeria";
};
/**
* Finds the telco provider for a given phone number prefix
*
* @public
* @param prefix - The phone number prefix (e.g., '0803', '07025')
* @returns The telco provider or null if prefix is not recognized
*
* @example
* ```typescript
* // Standard 4-digit prefixes
* getTelcoByPrefix('0803') // 'MTN'
* getTelcoByPrefix('0805') // 'GLO'
* getTelcoByPrefix('0802') // 'AIRTEL'
* getTelcoByPrefix('0809') // '9MOBILE'
*
* // 5-digit prefixes (MTN specific)
* getTelcoByPrefix('07025') // 'MTN'
* getTelcoByPrefix('07026') // 'MTN'
*
* // Unknown prefixes
* getTelcoByPrefix('9999') // null
*
* // Use in prefix validation
* function isValidPrefix(prefix: string): boolean {
* return getTelcoByPrefix(prefix) !== null;
* }
*
* // Build prefix-to-telco mapping
* const allPrefixes = getAllTelcos()
* .flatMap(telco => getTelcoInfo(telco).prefixes)
* .map(prefix => ({ prefix, telco: getTelcoByPrefix(prefix) }));
* ```
*/
declare const getTelcoByPrefix: (prefix: string) => TelcoProvider | null;
/**
* Normalizes a Nigerian phone number to a standardized format
* Ultra-fast implementation with minimal string allocations and early returns
*
* @public
* @template T - The desired phone format type
* @param phone - The phone number to normalize (accepts various formats)
* @param format - The desired output format (default: 'e164')
* @returns Normalized phone number in the specified format
* @throws {TypeError} When phone is not a string or is empty
* @throws {Error} When phone number format is invalid
*
* @example
* ```typescript
* // Basic usage
* normalizePhone('08031234567') // '+2348031234567'
* normalizePhone('8031234567') // '+2348031234567'
* normalizePhone('+2348031234567') // '+2348031234567'
*
* // Format conversion
* normalizePhone('08031234567', 'local') // '08031234567'
* normalizePhone('+2348031234567', 'local') // '08031234567'
* normalizePhone('08031234567', 'international') // '+2348031234567'
*
* // Handles various input formats
* normalizePhone('0803 123 4567') // '+2348031234567'
* normalizePhone('234-803-123-4567') // '+2348031234567'
* ```
*/
declare function normalizePhone<T extends PhoneFormat = 'e164'>(phone: string, format?: T): T extends 'local' ? LocalPrefix : T extends 'international' ? InternationalPrefix : E164Format;
/**
* Safe phone normalization that returns a Result type instead of throwing
* Ideal for scenarios where you want to handle errors gracefully
*
* @public
* @param phone - The phone number to normalize
* @param format - The desired output format (default: 'e164')
* @returns Result object with success/error information
*
* @example
* ```typescript
* const result = safeNormalizePhone('08031234567');
* if (result.success) {
* console.log('Normalized:', result.data); // '+2348031234567'
* } else {
* console.error('Error:', result.error);
* }
*
* // Batch processing example
* const phones = ['08031234567', 'invalid', '07051234567'];
* const results = phones.map(phone => safeNormalizePhone(phone));
* const valid = results.filter(r => r.success).map(r => r.data);
* ```
*/
declare function safeNormalizePhone(phone: string, format?: PhoneFormat): PhoneResult<string>;
/**
* Validates if a phone number is a valid Nigerian number
* Optimized validation with minimal overhead and branded return type
*
* @public
* @param phone - The phone number to validate
* @returns True if the phone number is valid, false otherwise
*
* @example
* ```typescript
* // Basic validation
* isValidNigerianNumber('08031234567') // true
* isValidNigerianNumber('123') // false
* isValidNigerianNumber('+2348031234567') // true
*
* // Type guard usage
* function processPhoneNumber(phone: string) {
* if (isValidNigerianNumber(phone)) {
* // TypeScript knows phone is ValidatedPhone here
* return sendSMS(phone); // Safe to use
* }
* throw new Error('Invalid phone number');
* }
*
* // Filter valid numbers from array
* const phones = ['08031234567', 'invalid', '07051234567'];
* const validPhones = phones.filter(isValidNigerianNumber);
* ```
*/
declare function isValidNigerianNumber(phone: string): phone is ValidatedPhone;
/**
* Detects the telecommunications provider for a Nigerian phone number
* Ultra-fast telco detection with prefix tree optimization for O(1) lookup
*
* @public
* @param phone - The phone number to analyze (any supported format)
* @returns The telecommunications provider or null if not detected
*
* @example
* ```typescript
* // Basic telco detection
* getTelco('08031234567') // 'MTN'
* getTelco('08051234567') // 'GLO'
* getTelco('08021234567') // 'AIRTEL'
* getTelco('08091234567') // '9MOBILE'
* getTelco('123') // null (invalid number)
*
* // Works with any format
* getTelco('+2348031234567') // 'MTN'
* getTelco('2348031234567') // 'MTN'
* getTelco('8031234567') // 'MTN'
*
* // Use with conditional logic
* const telco = getTelco(phoneNumber);
* if (telco === 'MTN') {
* console.log('MTN subscriber detected');
* } else if (telco) {
* console.log(`${telco} subscriber detected`);
* } else {
* console.log('Unknown or invalid number');
* }
* ```
*/
declare function getTelco(phone: string): TelcoProvider | null;
/**
* Extracts comprehensive information about a Nigerian phone number
* Provides detailed analysis including validation, telco detection, and formatting
* Results are cached for improved performance on repeated calls
*
* @public
* @param phone - The phone number to analyze (any supported format)
* @returns Comprehensive phone information object
*
* @example
* ```typescript
* // Basic usage
* const info = getPhoneInfo('08031234567');
* console.log(info);
* // {
* // original: '08031234567',
* // normalized: '+2348031234567',
* // telco: 'MTN',
* // isValid: true,
* // format: 'local',
* // prefix: '0803',
* // number: '8031234567'
* // }
*
* // Handle invalid numbers
* const invalidInfo = getPhoneInfo('123');
* console.log(invalidInfo.isValid); // false
* console.log(invalidInfo.telco); // null
*
* // Extract specific information
* const { telco, isValid, normalized } = getPhoneInfo(userInput);
* if (isValid) {
* console.log(`${telco} number: ${normalized}`);
* }
*
* // Batch analysis
* const phones = ['08031234567', '07051234567', '08021234567'];
* const analysis = phones.map(getPhoneInfo);
* const telcoCount = analysis.reduce((acc, info) => {
* if (info.telco) acc[info.telco] = (acc[info.telco] || 0) + 1;
* return acc;
* }, {} as Record<string, number>);
* ```
*/
declare function getPhoneInfo(phone: string): PhoneInfo;
/**
* Splits a Nigerian phone number into its component parts
* Optimized implementation with early return pattern for better performance
*
* @public
* @param phone - The phone number to split (any supported format)
* @returns Object containing prefix, telco provider, and number components
* @throws {Error} When phone number is invalid
*
* @example
* ```typescript
* // Basic usage
* const parts = splitPhoneParts('08031234567');
* console.log(parts);
* // {
* // prefix: '0803',
* // telco: 'MTN',
* // number: '8031234567'
* // }
*
* // Works with international format
* const parts2 = splitPhoneParts('+2347051234567');
* console.log(parts2);
* // {
* // prefix: '0705',
* // telco: 'GLO',
* // number: '7051234567'
* // }
*
* // Handle 5-digit prefixes (like MTN's 07025)
* const parts3 = splitPhoneParts('070251234567');
* console.log(parts3);
* // {
* // prefix: '07025',
* // telco: 'MTN',
* // number: '070251234567'
* // }
*
* // Use in validation workflows
* try {
* const { telco, prefix } = splitPhoneParts(userInput);
* console.log(`Number uses ${telco} with prefix ${prefix}`);
* } catch (error) {
* console.error('Invalid phone number:', error.message);
* }
* ```
*/
declare function splitPhoneParts(phone: string): PhoneParts;
/**
* Generates a random valid Nigerian phone number
* Uses cryptographically secure random generation for better randomness
*
* @public
* @param telco - Optional specific telco provider to generate for
* @returns A randomly generated valid Nigerian phone number in E.164 format
*
* @example
* ```typescript
* // Generate random number from any telco
* const randomPhone = generateRandomPhone();
* console.log(randomPhone); // '+2348031234567' (random)
*
* // Generate MTN number specifically
* const mtnPhone = generateRandomPhone('MTN');
* console.log(mtnPhone); // '+2348031234567' (MTN prefix)
*
* // Generate multiple numbers
* const phones = Array.from({ length: 5 }, () => generateRandomPhone());
* console.log(phones);
* // ['+2348031234567', '+2347051234567', '+2348021234567', ...]
*
* // Generate numbers for each telco
* const telcos: TelcoProvider[] = ['MTN', 'GLO', 'AIRTEL', '9MOBILE'];
* const phonesByTelco = telcos.map(telco => ({
* telco,
* phone: generateRandomPhone(telco)
* }));
*
* // Use in testing scenarios
* function testPhoneValidation() {
* const testPhone = generateRandomPhone();
* assert(isValidNigerianNumber(testPhone));
* assert(getTelco(testPhone) !== null);
* }
* ```
*/
declare function generateRandomPhone(telco?: TelcoProvider): ValidatedPhone;
/**
* Batch processing utilities for high-performance scenarios
* Optimized for processing large arrays of phone numbers efficiently
*/
/**
* Normalizes multiple phone numbers in batch with error handling
* More efficient than calling normalizePhone individually for large datasets
*
* @public
* @param phones - Array of phone numbers to normalize
* @param format - The desired output format for all numbers
* @returns Array of results with success/error information for each number
*
* @example
* ```typescript
* const phones = ['08031234567', 'invalid', '07051234567', '08021234567'];
* const results = batchNormalizePhones(phones, 'international');
*
* // Process results
* const successful = results.filter(r => r.success).map(r => r.data);
* const failed = results.filter(r => !r.success);
*
* console.log('Valid phones:', successful);
* // ['+2348031234567', '+2347051234567', '+2348021234567']
*
* console.log('Failed phones:', failed.map(f => f.error));
* // ['Invalid phone number format: invalid']
* ```
*/
declare function batchNormalizePhones(phones: readonly string[], format?: PhoneFormat): PhoneResult<string>[];
/**
* Detects telco providers for multiple phone numbers in batch
* Optimized for high-performance scenarios with large datasets
*
* @public
* @param phones - Array of phone numbers to analyze
* @returns Array of telco providers (null for invalid numbers)
*
* @example
* ```typescript
* const phones = ['08031234567', '07051234567', '08021234567', 'invalid'];
* const telcos = batchGetTelcos(phones);
* console.log(telcos); // ['MTN', 'GLO', 'AIRTEL', null]
*
* // Count telco distribution
* const telcoCount = telcos.reduce((acc, telco) => {
* if (telco) acc[telco] = (acc[telco] || 0) + 1;
* return acc;
* }, {} as Record<TelcoProvider, number>);
* ```
*/
declare function batchGetTelcos(phones: readonly string[]): (TelcoProvider | null)[];
/**
* Validates multiple phone numbers in batch
* Significantly faster than individual validation for large arrays
*
* @public
* @param phones - Array of phone numbers to validate
* @returns Array of boolean validation results
*
* @example
* ```typescript
* const phones = ['08031234567', 'invalid', '07051234567'];
* const validations = batchValidatePhones(phones);
* console.log(validations); // [true, false, true]
*
* // Filter valid phones
* const validPhones = phones.filter((_, index) => validations[index]);
* console.log(validPhones); // ['08031234567', '07051234567']
*
* // Get validation statistics
* const stats = {
* total: phones.length,
* valid: validations.filter(Boolean).length,
* invalid: validations.filter(v => !v).length
* };
* ```
*/
declare function batchValidatePhones(phones: readonly string[]): boolean[];
/**
* Format conversion utilities
*/
/**
* Converts a phone number to international format (+234...)
*
* @public
* @param phone - The phone number to convert (any supported format)
* @returns Phone number in international format
* @throws {Error} When phone number is invalid
*
* @example
* ```typescript
* // Convert local to international
* formatPhoneToInternational('08031234567') // '+2348031234567'
*
* // Already international (no change)
* formatPhoneToInternational('+2348031234567') // '+2348031234567'
*
* // From raw number
* formatPhoneToInternational('8031234567') // '+2348031234567'
*
* // Use in API calls
* function sendSMS(phone: string, message: string) {
* const internationalPhone = formatPhoneToInternational(phone);
* return smsProvider.send(internationalPhone, message);
* }
* ```
*/
declare const formatPhoneToInternational: (phone: string) => InternationalPrefix;
/**
* Converts a phone number to local format (0...)
*
* @public
* @param phone - The phone number to convert (any supported format)
* @returns Phone number in local format
* @throws {Error} When phone number is invalid
*
* @example
* ```typescript
* // Already local (no change)
* formatPhoneToLocal('08031234567') // '08031234567'
*
* // From international to local
* formatPhoneToLocal('+2348031234567') // '08031234567'
*
* // From raw number
* formatPhoneToLocal('8031234567') // '08031234567'
*
* // Use in local display
* function displayPhone(phone: string): string {
* const localPhone = formatPhoneToLocal(phone);
* // Format as: 0803 123 4567
* return localPhone.replace(/(\d{4})(\d{3})(\d{4})/, '$1 $2 $3');
* }
* ```
*/
declare const formatPhoneToLocal: (phone: string) => LocalPrefix;
/**
* Validates if a phone number matches a specific format
* More strict than general validation, checks exact format compliance
*
* @public
* @param phone - The phone number to validate
* @param format - The expected format to validate against
* @returns True if the phone number matches the specified format exactly
*
* @example
* ```typescript
* // Strict format validation
* validatePhoneFormat('08031234567', 'local') // true
* validatePhoneFormat('+2348031234567', 'local') // false (wrong format)
* validatePhoneFormat('+2348031234567', 'international') // true
* validatePhoneFormat('123', 'local') // false (invalid)
*
* // Use in input validation
* function validatePhoneInput(input: string, expectedFormat: PhoneFormat): boolean {
* return validatePhoneFormat(input, expectedFormat);
* }
*
* // Validate API input format
* function processPhoneNumber(phone: string, format: PhoneFormat) {
* if (!validatePhoneFormat(phone, format)) {
* throw new Error(`Phone number must be in ${format} format`);
* }
* return phone;
* }
* ```
*/
declare function validatePhoneFormat(phone: string, format: PhoneFormat): boolean;
export { type BatchBenchmarkResult, type BenchmarkResult, type E164Format, type InternationalPrefix, type LocalPrefix, type NormalizedPhone, type PerformanceStats, type PhoneFormat, type PhoneInfo, type PhoneParts, type PhoneResult, PhoneValidationError, type TelcoPrefix, type TelcoProvider, type ValidatedPhone, batchGetTelcos, batchNormalizePhones, batchValidatePhones, formatPhoneToInternational, formatPhoneToLocal, generateRandomPhone, getAllTelcos, getPhoneInfo, getTelco, getTelcoByPrefix, getTelcoInfo, isNormalizedPhone, isValidNigerianNumber, isValidatedPhone, normalizePhone, safeNormalizePhone, splitPhoneParts, validatePhoneFormat };