ip-utilities
v1.0.0
Published
IP address parsing, validation, and CIDR operations for TypeScript
Maintainers
Readme
ip-utilities
IP address parsing, validation, and CIDR operations for TypeScript.
Install
bun add ip-utilities
# or
npm install ip-utilitiesQuick 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 | CIDRv6All 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.tsLicense
MIT
