TypeScript

Type-safe usage of phoneng with discriminated unions, type guards, and proper type inference.

phoneng is written in TypeScript and ships complete type declarations. All types are exported and can be used directly in your code.

Discriminated Unions

ParseResult is a discriminated union that uses the valid property as the discriminant:

type ParseSuccess = {
  valid: true;
  e164: string;
  national: string;
  international: string;
  compact: string;
  rfc3966: string;
  prefix: string;
  network: Network;
  type: NumberType;
};

type ParseFailure = {
  valid: false;
  reason: ParseErrorCode;
  input: string;
};

type ParseResult = ParseSuccess | ParseFailure;

TypeScript narrows the type automatically when you check valid:

import { parse } from "phoneng";

const result = parse("08031234567");

if (result.valid) {
  result.e164; // string (accessible)
  result.network; // Network (accessible)
} else {
  result.reason; // ParseErrorCode (accessible)
  result.input; // string (accessible)
}

Type Imports

Import types directly from phoneng:

import {
  parse,
  parseMany,
  isValid,
  isPossible,
  type ParseResult,
  type ParseSuccess,
  type ParseFailure,
  type BatchResult,
  type Network,
  type NumberType,
  type ParseErrorCode,
} from "phoneng";

Type Guards

Create a type guard to narrow to ParseSuccess:

import { type ParseResult, type ParseSuccess } from "phoneng";

function isParseSuccess(result: ParseResult): result is ParseSuccess {
  return result.valid;
}

const result = parse("08031234567");

if (isParseSuccess(result)) {
  console.log(result.e164);
}

Working with Network Type

The Network type is a union of string literals:

import { type Network } from "phoneng";

const network: Network = "MTN";

function getNetworkColor(network: Network): string {
  switch (network) {
    case "MTN":
      return "#ffcc00";
    case "AIRTEL":
      return "#e60000";
    case "GLO":
      return "#00a550";
    case "NINE_MOBILE":
      return "#006666";
    default:
      return "#6b7280";
  }
}

Working with NumberType

The NumberType discriminates between mobile, landline, and special numbers:

import { parse, type NumberType } from "phoneng";

function isMobile(result: ParseResult): boolean {
  return result.valid && result.type === "MOBILE";
}

Exhaustive Error Handling

Use TypeScript’s exhaustive checking to ensure all error codes are handled:

import { parse, type ParseErrorCode } from "phoneng";

function getErrorMessage(code: ParseErrorCode): string {
  switch (code) {
    case "EMPTY_INPUT":
      return "Please enter a phone number";
    case "INVALID_CHARACTERS":
      return "Only digits are allowed";
    case "INVALID_LENGTH":
      return "Invalid number length";
    case "INVALID_COUNTRY_CODE":
      return "Not a Nigerian number";
    case "INVALID_PREFIX":
      return "Unknown network prefix";
    case "TOO_SHORT":
      return "Number is too short";
    case "TOO_LONG":
      return "Number is too long";
    case "NOT_NIGERIAN":
      return "Must be Nigerian";
    default:
      const _exhaustive: never = code;
      throw new Error(`Unhandled error code: ${_exhaustive}`);
  }
}

If a new error code is added to phoneng, TypeScript will show a compile error.

Generic Helpers

Create generic functions that work with ParseResult:

import { parse, type ParseResult, type ParseSuccess } from "phoneng";

function mapSuccess<T>(
  result: ParseResult,
  fn: (success: ParseSuccess) => T,
): T | null {
  return result.valid ? fn(result) : null;
}

const e164 = mapSuccess(parse("08031234567"), (r) => r.e164);

Filtering Valid Results

Filter an array to only valid results with proper typing:

import { parseMany, type ParseSuccess } from "phoneng";

const batch = parseMany(["08031234567", "invalid"]);

const validResults: ParseSuccess[] = batch.results.filter(
  (r): r is ParseSuccess => r.valid,
);

validResults.forEach((r) => {
  console.log(r.e164);
});

React Props

Type component props with phoneng types:

import { type ParseSuccess, type Network } from 'phoneng';

interface PhoneDisplayProps {
  phone: ParseSuccess;
}

function PhoneDisplay({ phone }: PhoneDisplayProps) {
  return (
    <div>
      <p>{phone.national}</p>
      <p>{phone.network}</p>
    </div>
  );
}

interface NetworkBadgeProps {
  network: Network;
}

function NetworkBadge({ network }: NetworkBadgeProps) {
  return <span className={`badge-${network.toLowerCase()}`}>{network}</span>;
}

Zod with Type Inference

Infer types from Zod schemas that use phoneng:

import { z } from "zod";
import { parse, type ParseSuccess } from "phoneng";

const phoneSchema = z.string().transform((val, ctx) => {
  const result = parse(val);
  if (!result.valid) {
    ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.reason });
    return z.NEVER;
  }
  return result;
});

type PhoneData = z.infer<typeof phoneSchema>;

const formSchema = z.object({
  name: z.string(),
  phone: phoneSchema,
});

type FormData = z.infer<typeof formSchema>;

Strict Mode Compatibility

phoneng is designed for TypeScript strict mode. All functions have explicit return types and no implicit any:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

Module Resolution

For best results, use moduleResolution: "bundler" or "node16":

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "module": "ESNext",
    "target": "ES2022"
  }
}

This ensures proper resolution of phoneng’s ESM exports.

MIT License GitHub · npm

Network data from NCC allocations. MNP may affect actual carriers.