npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

phoneshield

v1.3.0

Published

A lightning-fast, privacy-first phone intelligence engine. Smarter validation, zero-knowledge lookups, and built-in fraud detection.

Readme

PhoneShield

A lightning-fast, privacy-first phone intelligence engine. A smarter, tree-shakable alternative to libphonenumber-js and numverify — works everywhere JavaScript runs: Node.js, browsers, React, Vue, Svelte, plain HTML.

Features

  • < 5KB per country — ESM-first, tree-shakable. Import only what you need.
  • Framework-agnostic — Core engine has zero dependencies. Use it in Node, Deno, Bun, or any frontend framework.
  • Hybrid validation — Local regex + length checks, no network calls required.
  • "Did You Mean?" — Suggests corrections when a number is off by 1–2 digits.
  • Intelligence schema — Returns isValid, E.164 format, country, lineType, and riskScore.
  • Privacy-first — SHA-256 hashing for zero-knowledge spam database lookups.
  • Real-time formatting — Built-in stateful formatter with debounce (framework-agnostic).
  • Optional React hookusePhoneShield available via phoneshield/react (React is never required).
  • TypeScript native — Full type safety and IntelliSense out of the box.

Installation

npm install phoneshield

Quick Start

Country-Specific Validation (Tree-Shakable)

Import only the country you need — each entry point pulls in just that country's metadata:

import { validateUS } from 'phoneshield/us';

const result = validateUS('(202) 555-1234');
// {
//   isValid: true,
//   format: '+12025551234',
//   country: 'US',
//   lineType: 'Mobile',
//   riskScore: 0.0
// }

Multi-Country

import { validateUS } from 'phoneshield/us';
import { validateUK } from 'phoneshield/uk';
import { validateFR } from 'phoneshield/fr';
import { validateIN } from 'phoneshield/in';

validateUS('2025551234');
validateUK('7400123456');
validateFR('612345678');
validateIN('9876543210');

Generic Validation (Any Country)

import { validate, getMetadata } from 'phoneshield';

const metadata = getMetadata('DE');
const result = validate('15112345678', metadata, {
  enableSuggestions: true,
});

Supported Countries

| Code | Country | Dial Code | Import Path | | ---- | ------- | --------- | ----------- | | US | United States | +1 | phoneshield/us | | CA | Canada | +1 | phoneshield/ca | | UK | United Kingdom | +44 | phoneshield/uk | | AU | Australia | +61 | phoneshield/au | | DE | Germany | +49 | phoneshield/de | | FR | France | +33 | phoneshield/fr | | JP | Japan | +81 | phoneshield/jp | | IN | India | +91 | phoneshield/in |

Validation Result Schema

Every validation call returns a ValidationResult:

interface ValidationResult {
  isValid: boolean;            // Pass/fail
  format: string | null;       // E.164 format (e.g. "+12025551234")
  country: CountryCode | null; // "US", "UK", "FR", etc.
  lineType: LineType;          // "Mobile" | "Landline" | "VoIP" | "TollFree" | "Premium" | "Unknown"
  riskScore: number;           // 0.0 (safe) to 1.0 (high risk)
  suggestion?: string;         // "Did you mean (202) 555-1234?"
  errors?: string[];           // Human-readable error messages
}

"Did You Mean?" Engine

When a number is off by 1–2 digits, PhoneShield suggests the closest valid number:

import { validateUS } from 'phoneshield/us';

const result = validateUS('202555123'); // 9 digits instead of 10
console.log(result.isValid);     // false
console.log(result.suggestion);  // "(202) 555-1234"

Risk Scoring

PhoneShield scores numbers from 0.0 (safe) to 1.0 (high risk) based on:

  • Invalid format/length — +0.5
  • Premium numbers (e.g. 900) — +0.3
  • VoIP numbers — +0.2
  • Unknown line type — +0.25
  • Repeating digits (e.g. 5555555) — +0.15
  • Sequential patterns (e.g. 123456) — +0.1
  • Suspicious prefixes (000, 999) — +0.2
import { validateUS } from 'phoneshield/us';

const result = validateUS('9001234567');
console.log(result.riskScore); // 0.3 (Premium number)
console.log(result.lineType);  // "Premium"

Line Type Detection

import { validateUS } from 'phoneshield/us';

validateUS('2025551234').lineType;  // "Mobile" or "Landline"
validateUS('8005551234').lineType;  // "TollFree"
validateUS('9005551234').lineType;  // "Premium"

Privacy-First Hashing

Generate SHA-256 hashes to query spam databases without exposing the actual number:

import { hashPhoneNumber } from 'phoneshield';

const hash = await hashPhoneNumber('+12025551234');
// "a3f2b8c1..." — send this to your spam API, not the raw number

Uses the Web Crypto API (browser + Node 18+). No external dependencies.

Real-Time Formatting (Framework-Agnostic)

The createPhoneFormatter() engine works in any JavaScript environment — no React, no framework needed:

import { createPhoneFormatter } from 'phoneshield';

const formatter = createPhoneFormatter('US', {
  debounceMs: 300,
  enableSuggestions: true,
  onStateChange: (state) => {
    console.log(state.formattedValue); // "(202) 555-1234"
    console.log(state.validation);     // ValidationResult or null
    console.log(state.isValidating);   // true/false
  },
});

// Feed input as the user types
formatter.handleInput('2');
formatter.handleInput('20');
formatter.handleInput('202');
formatter.handleInput('2025551234');

// Read state at any time
const state = formatter.getState();

// Clean up
formatter.clear();
formatter.destroy();

Usage with Vue

import { ref, onMounted, onUnmounted } from 'vue';
import { createPhoneFormatter } from 'phoneshield';

const formattedValue = ref('');
const validation = ref(null);

let formatter;

onMounted(() => {
  formatter = createPhoneFormatter('FR', {
    debounceMs: 250,
    onStateChange: (state) => {
      formattedValue.value = state.formattedValue;
      validation.value = state.validation;
    },
  });
});

onUnmounted(() => formatter?.destroy());

function onInput(e) {
  formatter?.handleInput(e.target.value);
}

Usage with Svelte

import { createPhoneFormatter } from 'phoneshield';
import { onDestroy } from 'svelte';

let formattedValue = '';
let validation = null;

const formatter = createPhoneFormatter('DE', {
  onStateChange: (state) => {
    formattedValue = state.formattedValue;
    validation = state.validation;
  },
});

onDestroy(() => formatter.destroy());

function handleInput(e) {
  formatter.handleInput(e.target.value);
}

Usage in Node.js / Backend

import { validateUS } from 'phoneshield/us';
import { hashPhoneNumber } from 'phoneshield';

// Validate incoming phone number
const result = validateUS(req.body.phone);

if (!result.isValid) {
  return res.status(400).json({ errors: result.errors });
}

// Store only the hash
const hash = await hashPhoneNumber(result.format);
await db.users.update({ phoneHash: hash });

React Hook (Optional)

Install React as usual — it's an optional peer dependency. Import from the dedicated subpath:

import { usePhoneShield } from 'phoneshield/react';

function PhoneInput() {
  const {
    formattedValue,
    validation,
    isValidating,
    handleChange,
    clear,
  } = usePhoneShield('US', { debounceMs: 300, enableSuggestions: true });

  return (
    <div>
      <input
        type="tel"
        value={formattedValue}
        onChange={(e) => handleChange(e.target.value)}
        placeholder="(555) 123-4567"
      />

      {isValidating && <span>Validating...</span>}

      {validation && (
        <div>
          <p>Valid: {validation.isValid ? 'Yes' : 'No'}</p>
          <p>Type: {validation.lineType}</p>
          <p>Risk: {(validation.riskScore * 100).toFixed(0)}%</p>
          {validation.suggestion && (
            <p>Did you mean: {validation.suggestion}?</p>
          )}
        </div>
      )}

      <button onClick={clear}>Clear</button>
    </div>
  );
}

Formatting Utilities

import { normalizePhoneNumber, formatPhoneNumber, toE164 } from 'phoneshield';
import { US_METADATA } from 'phoneshield/us';

normalizePhoneNumber('(202) 555-1234');
// "2025551234"

formatPhoneNumber('2025551234', US_METADATA);
// "(202) 555-1234"

toE164('2025551234', US_METADATA);
// "+12025551234"

Custom Country Metadata

Add your own country by implementing the CountryMetadata interface:

import { CountryMetadata, validate } from 'phoneshield';

const MY_METADATA: CountryMetadata = {
  countryCode: 'MY' as any,
  dialCode: '+60',
  patterns: {
    mobile: [/^1[0-46-9]\d{7,8}$/],
    landline: [/^[3-9]\d{7}$/],
    voip: [],
    tollFree: [/^1800\d{6}$/],
    premium: [],
  },
  lengths: [9, 10],
  format: (digits) =>
    digits.length === 10
      ? `${digits.slice(0, 3)}-${digits.slice(3, 6)} ${digits.slice(6)}`
      : digits,
};

const result = validate('123456789', MY_METADATA);

API Reference

Core

| Function | Description | | -------- | ----------- | | validate(input, metadata, options?) | Full validation with intelligence schema | | validateUS(phone, options?) | US-specific (tree-shakable) | | validateUK(phone, options?) | UK-specific | | validateCA(phone, options?) | Canada-specific | | validateAU(phone, options?) | Australia-specific | | validateDE(phone, options?) | Germany-specific | | validateFR(phone, options?) | France-specific | | validateJP(phone, options?) | Japan-specific | | validateIN(phone, options?) | India-specific | | getMetadata(country) | Get metadata for a country code |

Formatting

| Function | Description | | -------- | ----------- | | normalizePhoneNumber(input) | Strip all non-digit characters | | formatPhoneNumber(digits, metadata) | Format to local display format | | toE164(digits, metadata) | Format to E.164 international format |

Real-Time Formatter

| Function | Description | | -------- | ----------- | | createPhoneFormatter(country?, options?) | Create a stateful formatter instance |

Returns a PhoneFormatter with:

  • handleInput(input) — Process new input
  • getState() — Get current { value, formattedValue, validation, isValidating }
  • clear() — Reset state
  • destroy() — Clean up timers

Privacy

| Function | Description | | -------- | ----------- | | hashPhoneNumber(phone) | SHA-256 hash (async, returns hex string) | | createZKProof(phone) | Alias for hashPhoneNumber |

React (Optional)

import { usePhoneShield } from 'phoneshield/react';

| Hook | Description | | ---- | ----------- | | usePhoneShield(country?, options?) | Real-time formatting + validation hook |

Returns { value, formattedValue, validation, isValidating, handleChange, clear }.

Options

interface PhoneShieldOptions {
  defaultCountry?: CountryCode;  // Fallback country
  strictMode?: boolean;          // Stricter pattern matching
  enableSuggestions?: boolean;   // Enable "Did You Mean?" (default: true)
}

For createPhoneFormatter and usePhoneShield, you can also pass:

  • debounceMs — Debounce delay in ms (default: 300)

Bundle Size

| Import | Size | | ------ | ---- | | phoneshield/us | ~1.2 KB | | phoneshield/fr | ~1.0 KB | | Any single country | < 2 KB | | phoneshield (all countries) | ~3.5 KB | | phoneshield/react | ~15 KB (includes core) |

Measured with tsup tree-shaking enabled. Actual sizes depend on your bundler.

Compatibility

  • Node.js 18+ (uses crypto.subtle for hashing)
  • Browsers: Chrome 37+, Firefox 34+, Safari 11+, Edge 79+
  • Deno, Bun — works out of the box
  • React 17+ (optional, for phoneshield/react only)

Contributing

We welcome contributions! Here's how to get started:

Development Setup

  1. Fork and clone the repository

    git clone https://github.com/youssefbrr/PhoneShield.git
    cd PhoneShield
  2. Install dependencies

    npm install
  3. Build the package

    npm run build
  4. Run tests

    npm test

Project Structure

src/
├── core/           # Core validation engine
├── countries/      # Country-specific metadata
├── formatters/     # Phone number formatting utilities
├── privacy/        # Hashing and privacy features
└── react/          # React hooks (optional)

Adding a New Country

  1. Create metadata file in src/countries/[country].ts
  2. Define patterns for mobile, landline, VoIP, toll-free, and premium numbers
  3. Add formatting function
  4. Export from src/countries/index.ts
  5. Add tests
  6. Update README with new country

Making Changes

  1. Create a feature branch: git checkout -b feature/your-feature
  2. Make your changes
  3. Add tests for new functionality
  4. Ensure all tests pass: npm test
  5. Build to verify: npm run build
  6. Commit with descriptive message: git commit -m "feat: add feature description"
  7. Push and create a Pull Request

Commit Convention

We follow Conventional Commits:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • test: Adding or updating tests
  • refactor: Code refactoring
  • perf: Performance improvements
  • chore: Maintenance tasks

Pull Request Guidelines

  • Keep PRs focused on a single feature or fix
  • Include tests for new functionality
  • Update documentation as needed
  • Ensure all tests pass
  • Follow existing code style

License

MIT