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

ip-utilities

v1.0.0

Published

IP address parsing, validation, and CIDR operations for TypeScript

Readme

ip-utilities

IP address parsing, validation, and CIDR operations for TypeScript.

Install

bun add ip-utilities
# or
npm install ip-utilities

Quick Start

import {
  parseIP, isIP, isIPOrCIDR,
  isPrivateIP, isPublicIPOrCIDR,
  parseCIDR, isIPInCIDR,
  sortIPs,
} from 'ip-utilities'

isIP('192.168.1.1')              // true
isIP('::1')                      // true
isIPOrCIDR('10.0.0.0/8')        // true
isPrivateIP('192.168.1.1')      // true
isPublicIPOrCIDR('8.8.8.8')     // true
isIPInCIDR('10.0.0.5', '10.0.0.0/8') // true

const addr = parseIP('192.168.1.1')
// { ip: 3232235777, kind: 4 }

const cidr = parseCIDR('::1/128')
// { addr: { w0: 0, w1: 0, w2: 0, w3: 1, kind: 6 }, prefix: 128, kind: 6 }

sortIPs(['::1', '192.168.1.1', '10.0.0.1', '2001:db8::1'])
// ['10.0.0.1', '192.168.1.1', '::1', '2001:db8::1']

Sub-path Imports

Each module is available as a separate entry point for tree-shaking:

import { parseIPv4, isIPv4 } from 'ip-utilities/ipv4'
import { parseIPv6, ipv6ToString } from 'ip-utilities/ipv6'
import { parseCIDR, ipInCIDR } from 'ip-utilities/cidr'
import { ipv4Range, isPrivateIPv6 } from 'ip-utilities/ranges'
import { sortIPs, compareIPs } from 'ip-utilities/sort'
import ipRegex, { testV4 } from 'ip-utilities/regex'

API

Validation

| Function | Description | |---|---| | isIP(s) | Checks if a string is a valid IPv4 or IPv6 address | | isIPv4(s) | Checks if a string is a valid IPv4 address | | isIPv6(s) | Checks if a string is a valid IPv6 address | | isCIDR(s) | Checks if a string is valid CIDR notation | | isIPOrCIDR(s) | Checks if a string is a valid IP address or CIDR | | isPrivateIP(s) | Checks if a string is a valid IP in a private range | | isPrivateIPOrCIDR(s) | Checks if a valid IP/CIDR is in a private range | | isPublicIPOrCIDR(s) | Checks if a valid IP/CIDR is globally routable | | detectIPType(s) | Returns 4, 6, or null |

Parsing

| Function | Returns | Description | |---|---|---| | parseIP(s) | IPAddr \| null | Auto-detects and parses IPv4 or IPv6 | | parseIPv4(s) | IPv4Addr \| null | Parses strict dotted-decimal IPv4 | | parseIPv4Loose(s) | IPv4Addr \| null | Parses IPv4 with hex, octal, and classful shorthand | | parseIPv6(s) | IPv6Addr \| null | Parses IPv6 with ::, embedded IPv4, and zone IDs | | parseCIDR(s) | CIDR \| null | Auto-detects and parses IPv4 or IPv6 CIDR | | parseCIDRv4(s) | CIDRv4 \| null | Parses IPv4 CIDR notation | | parseCIDRv6(s) | CIDRv6 \| null | Parses IPv6 CIDR notation |

String Conversion

| Function | Returns | Description | |---|---|---| | ipv4ToString(ip) | string | Packed uint32 to dotted-decimal (e.g. "192.168.1.1") | | ipv4ToOctets(ip) | [n, n, n, n] | Packed uint32 to four octets | | ipv6ToString(addr) | string | Compressed RFC 5952 notation (e.g. "::1") | | ipv6ToNormalizedString(addr) | string | Uncompressed without padding (e.g. "0:0:0:0:0:0:0:1") | | ipv6ToFixedString(addr) | string | Zero-padded groups (e.g. "0000:0000:...:0001") |

Range Classification

| Function | Returns | Description | |---|---|---| | ipv4Range(ip) | IPv4RangeName | Classifies into IANA range (e.g. "private", "unicast") | | ipv6Range(addr) | IPv6RangeName | Classifies into IANA range (e.g. "uniqueLocal", "unicast") | | isPrivateIPv4(ip) | boolean | Checks RFC 1918 ranges (10/8, 172.16/12, 192.168/16) | | isPrivateIPv6(addr) | boolean | Checks unique-local, multicast, link-local, loopback | | isLoopbackIPv4(ip) | boolean | Checks 127.0.0.0/8 | | isLoopbackIPv6(addr) | boolean | Checks ::1 | | isLinkLocalIPv4(ip) | boolean | Checks 169.254.0.0/16 | | isLinkLocalIPv6(addr) | boolean | Checks fe80::/10 |

CIDR Operations

| Function | Returns | Description | |---|---|---| | ipInCIDR(ip, cidr) | boolean | Checks if a parsed IP is within a parsed CIDR range | | ipInCIDRv4(ip, cidr) | boolean | IPv4-specific CIDR containment check | | ipInCIDRv6(ip, cidr) | boolean | IPv6-specific CIDR containment check | | isIPInCIDR(ipStr, cidrStr) | boolean | Parses both strings and checks containment | | networkAddressV4(cidr) | IPv4Addr | First address in an IPv4 CIDR range | | broadcastAddressV4(cidr) | IPv4Addr | Last address in an IPv4 CIDR range | | networkAddressV6(cidr) | IPv6Addr | First address in an IPv6 CIDR range | | broadcastAddressV6(cidr) | IPv6Addr | Last address in an IPv6 CIDR range | | subnetMaskV4(prefix) | number | Subnet mask for a prefix length (0-32) | | prefixFromSubnetMask(mask) | number \| null | Prefix length from a contiguous subnet mask |

IPv6 Utilities

| Function | Returns | Description | |---|---|---| | isIPv4Mapped(addr) | boolean | Checks if address is IPv4-mapped (::ffff:0:0/96) | | ipv6ToIPv4(addr) | number | Extracts the embedded IPv4 uint32 from lower 32 bits | | ipv6ToWords(addr) | [x8 number] | Expands to eight 16-bit groups | | ipv6GetZoneId(addr) | string | Retrieves the zone ID (e.g. "eth0") or empty string |

Sorting

| Function | Returns | Description | |---|---|---| | sortIPs(ips) | string[] | Sorts IP/CIDR strings in network order (new array) | | compareIPs(a, b) | number | Comparator for two IP/CIDR strings (cached) | | clearSortCache() | void | Clears the internal comparison cache |

Sort order: IPv4 before IPv6, numeric within each version, invalid strings last (lexicographic among themselves), CIDR entries with the same network sort by prefix length.

Regex

import ipRegex, { testV4, testV6, testV46 } from 'ip-utilities/regex'

| Export | Description | |---|---| | testV4(s) | Fast exact IPv4 string test | | testV6(s) | Fast exact IPv6 string test | | testV46(s) | Fast exact IPv4-or-IPv6 string test | | ipRegex(options?) | Returns a RegExp matching IPv4 and IPv6 | | ipRegex.v4(options?) | Returns a RegExp matching IPv4 only | | ipRegex.v6(options?) | Returns a RegExp matching IPv6 only | | ipRegex.scanAll(input, options?) | Extracts all IP addresses from a string | | ipRegex.scanAllV4(input, options?) | Extracts all IPv4 addresses from a string | | ipRegex.scanAllV6(input, options?) | Extracts all IPv6 addresses from a string |

Options:

  • exact -- Match the entire string only (anchored)
  • includeBoundaries -- Require whitespace or string boundaries around matches

ScanResult:

{ value: string, index: number }

Types

interface IPv4Addr {
  readonly ip: number    // Packed uint32
  readonly kind: 4
}

interface IPv6Addr {
  readonly w0: number    // Bits 0-31
  readonly w1: number    // Bits 32-63
  readonly w2: number    // Bits 64-95
  readonly w3: number    // Bits 96-127
  readonly kind: 6
}

type IPAddr = IPv4Addr | IPv6Addr

interface CIDRv4 {
  readonly addr: IPv4Addr
  readonly prefix: number
  readonly kind: 4
}

interface CIDRv6 {
  readonly addr: IPv6Addr
  readonly prefix: number
  readonly kind: 6
}

type CIDR = CIDRv4 | CIDRv6

All types use a kind discriminant for narrowing:

const addr = parseIP(input)
if (addr?.kind === 4) {
  // addr is IPv4Addr
  console.log(ipv4ToString(addr.ip))
} else if (addr?.kind === 6) {
  // addr is IPv6Addr
  console.log(ipv6ToString(addr))
}

Benchmarks

Compared to ipaddr.js (v2.3.0) on AMD Ryzen 9 7950X, Bun 1.3:

| Operation | ip-utilities | ipaddr.js | Speedup | |---|---|---|---| | IPv4 parse (10 addrs) | 180 ns | 1,910 ns | 10.6x | | IPv6 parse (10 addrs) | 310 ns | 5,140 ns | 16.6x | | isValid (20 mixed) | 533 ns | 8,310 ns | 15.6x | | parse (20 mixed) | 593 ns | 15,960 ns | 26.9x | | CIDR match (strings) | 53 ns | 2,140 ns | 40.8x | | parseCIDR IPv4 | 26 ns | 1,930 ns | 74.4x | | parseCIDR IPv6 | 33 ns | 896 ns | 27.2x | | invalid IPv4 | 33 ns | 4,950 ns | 151.7x | | invalid IPv6 | 52 ns | 5,280 ns | 101.8x |

Run benchmarks locally:

bun run test/bench/ip.bench.ts
bun run test/bench/memory.bench.ts

License

MIT