Package Exports
- @h3mantd/ip-kit
Readme
IP Toolkit
TypeScript IP Toolkit for IPv4/IPv6 math, CIDR operations, ranges, allocation, and trie lookups.
Documentation
- API Reference - Complete API documentation
- Usage Examples - Practical examples for all features
- IP Calculations Guide - Deep dive into the mathematical foundations and BigInt-based calculations
- Contributing Guide - Development setup and contribution guidelines
Quick Start
import { ip, cidr, IPv4, CIDR } from 'ip-kit';
// Parse IPs
const ipv4 = ip('192.168.1.1');
const ipv6 = ip('2001:db8::1');
// Parse CIDRs
const network = cidr('192.168.1.0/24');
// Get network info
console.log(network.network().toString()); // 192.168.1.0
console.log(network.broadcast().toString()); // 192.168.1.255
console.log(network.size()); // 256n
// Check containment
console.log(network.contains(ip('192.168.1.50'))); // true
// Iterate hosts
for (const host of network.hosts()) {
console.log(host.toString());
}
// Subnetting
// Prefer generator usage for large splits to avoid materializing large arrays:
// Good (generator): iterate lazily
let count = 0;
for (const s of network.subnets(26)) {
count++;
}
console.log(count); // 4
// Avoid using `split()` on very large networks — it returns an array and
// can allocate millions of CIDR objects for IPv6-sized spaces.
// If you really need all entries in memory, use `split()`:
// const subnets = network.split(26);
// console.log(subnets.length); // 4
💡 See more examples in the
examples/
directory!
Installation
Prerequisites
- Node.js 18 or higher (BigInt support required)
- npm, pnpm or yarn
Install
# Using npm
npm install @h3mantd/ip-kit
# Using pnpm
pnpm add @h3mantd/ip-kit
# Using yarn
yarn add @h3mantd/ip-kit
Build from Source
git clone https://github.com/h3mantD/ip-kit.git
cd ip-kit
npm install
npm run build
Features
- ✅ IPv4/IPv6 parsing and formatting (string, number, bigint, bytes)
- ✅ CIDR operations (network, broadcast, contains, overlaps)
- ✅ Host iteration with proper /31 and /127 handling
- ✅ Subnetting and splitting
- ✅ IP ranges and CIDR conversion
- ✅ Range set operations (union, intersect, subtract)
- ✅ IP address allocation with conflict detection
- ✅ Radix trie for longest-prefix matching
- ✅ BigInt-based math for precision
- ✅ Lazy iterators for memory efficiency
- ✅ TypeScript strict mode with full type safety
- ✅ Dual ESM/CJS builds with TypeScript declarations
- ✅ Comprehensive test coverage (106+ tests)
API Reference
Core Types
type IPVersion = 4 | 6;
IP Classes
IPv4
class IPv4 extends IP<4> {
// Static methods
static parse(input: string | number | bigint | Uint8Array): IPv4;
static fromBigInt(value: bigint): IPv4;
// Instance methods
toBigInt(): bigint;
toBytes(): Uint8Array;
toString(): string;
equals(other: IP): boolean;
compare(other: IPv4): -1 | 0 | 1;
}
IPv6
class IPv6 extends IP<6> {
// Static methods
static parse(input: string | number | bigint | Uint8Array): IPv6;
static fromBigInt(value: bigint): IPv6;
// Instance methods
toBigInt(): bigint;
toBytes(): Uint8Array;
toString(): string; // RFC 5952 normalized
equals(other: IP): boolean;
compare(other: IPv6): -1 | 0 | 1;
}
Factory Functions
function ip(input: string | number | bigint | Uint8Array): IPv4 | IPv6;
function cidr(input: string): CIDR<4> | CIDR<6>;
CIDR Classes
class CIDR<V extends IPVersion = IPVersion> {
readonly ip: V extends 4 ? IPv4 : IPv6;
readonly prefix: number;
readonly version: V;
// Static methods
static parse(s: string): CIDR<4> | CIDR<6>;
static from(ip: IPv4, prefix: number): CIDR<4>;
static from(ip: IPv6, prefix: number): CIDR<6>;
// Instance methods
bits(): 32 | 128;
network(): typeof this.ip;
broadcast(): IPv4; // IPv4 only
size(): bigint;
firstHost(opts?: { includeEdges?: boolean }): typeof this.ip;
lastHost(opts?: { includeEdges?: boolean }): typeof this.ip;
contains(x: IP | CIDR): boolean;
overlaps(other: CIDR<V>): boolean;
*hosts(opts?: { includeEdges?: boolean }): Generator<typeof this.ip>;
*subnets(newPrefix: number): Generator<CIDR<V>>;
split(parts: number): CIDR<V>[];
move(n: number): CIDR<V>;
toRange(): IPRange<V>;
toPtr(): string[];
toString(): string;
}
Range Classes
class IPRange<V extends IPVersion = IPVersion> {
readonly start: V extends 4 ? IPv4 : IPv6;
readonly end: V extends 4 ? IPv4 : IPv6;
readonly version: V;
// Static methods
static parse(s: string): IPRange<4> | IPRange<6>;
static from(start: IPv4, end: IPv4): IPRange<4>;
static from(start: IPv6, end: IPv6): IPRange<6>;
// Instance methods
size(): bigint;
overlaps(other: IPRange<V>): boolean;
contains(ip: IP): boolean;
*ips(limit?: number): Generator<typeof this.start>;
toCIDRs(): CIDR<V>[];
toString(): string;
}
RangeSet Classes
class RangeSet<V extends IPVersion = IPVersion> {
// Static methods
static fromCIDRs<V extends IPVersion>(cidrs: Array<CIDR<V> | string>): RangeSet<V>;
static fromRanges<V extends IPVersion>(ranges: Array<IPRange<V>>): RangeSet<V>;
// Instance methods
isEmpty(): boolean;
size(): bigint;
union(other: RangeSet<V>): RangeSet<V>;
intersect(other: RangeSet<V>): RangeSet<V>;
subtract(other: RangeSet<V>): RangeSet<V>;
contains(ip: IP<V>): boolean;
containsCIDR(cidr: CIDR<V>): boolean;
*ips(limit?: number): Generator<IP<V>>;
toCIDRs(): CIDR<V>[];
toString(): string;
}
Allocator Classes
class Allocator<V extends IPVersion = IPVersion> {
readonly parent: CIDR<V>;
readonly taken: RangeSet<V>;
readonly version: V;
constructor(parent: CIDR<V>, taken?: RangeSet<V>);
// Allocation methods
nextAvailable(from?: IP<V>): IP<V> | null;
allocateNext(): IP<V> | null;
allocateIP(ip: IP<V>): boolean;
allocateCIDR(cidr: CIDR<V>): boolean;
// Query methods
freeBlocks(opts?: { minPrefix?: number; maxResults?: number }): CIDR<V>[];
availableCount(): bigint;
utilization(): number;
}
RadixTrie Classes
class RadixTrie<V extends IPVersion = IPVersion, T = unknown> {
readonly version: V;
constructor(version: V);
// Core methods
insert(cidr: CIDR<V>, value?: T): this;
remove(cidr: CIDR<V>): this;
longestMatch(ip: IP<V>): { cidr: CIDR<V>; value?: T } | null;
// Utility methods
isEmpty(): boolean;
size(): number;
getCIDRs(): CIDR<V>[];
}
Usage Examples
IPv4 Operations
import { IPv4, CIDR } from 'ip-kit';
// Parse different formats
const ip1 = IPv4.parse('192.168.1.1');
const ip2 = IPv4.parse(3232235777); // number
const ip3 = IPv4.parse(3232235777n); // bigint
const ip4 = IPv4.parse(new Uint8Array([192, 168, 1, 1])); // bytes
// CIDR operations
const cidr = CIDR.parse('192.168.1.0/24');
console.log(cidr.network().toString()); // '192.168.1.0'
console.log(cidr.broadcast().toString()); // '192.168.1.255'
console.log(cidr.size()); // 256n
// Check containment
console.log(cidr.contains(IPv4.parse('192.168.1.100'))); // true
// Iterate hosts (excludes network/broadcast for /24)
for (const host of cidr.hosts()) {
console.log(host.toString());
}
// Subnet into /26 networks
const subnets = Array.from(cidr.subnets(26));
console.log(subnets.map((s) => s.toString()));
// ['192.168.1.0/26', '192.168.1.64/26', '192.168.1.128/26', '192.168.1.192/26']
IPv6 Operations
import { IPv6, CIDR } from 'ip-kit';
// Parse with compression
const ip = IPv6.parse('2001:0db8:0000:0000:0000:0000:0000:0001');
console.log(ip.toString()); // '2001:db8::1'
// CIDR operations
const cidr = CIDR.parse('2001:db8::/32');
console.log(cidr.size()); // 79228162514264337593543950336n
// IPv6 always includes all addresses in hosts()
// Use the generator form to avoid materializing huge arrays:
for (const h of cidr.hosts({ includeEdges: true })) {
// process host lazily
break; // example: stop after first
}
IP Ranges
import { IPRange, IPv4 } from 'ip-kit';
// Parse range
const range = IPRange.parse('192.168.1.10 - 192.168.1.20');
console.log(range.size()); // 11n
// Check containment
console.log(range.contains(IPv4.parse('192.168.1.15'))); // true
// Iterate IPs
for (const ip of range.ips()) {
console.log(ip.toString());
}
// Convert to minimal CIDRs
const cidrs = range.toCIDRs();
console.log(cidrs.map((c) => c.toString()));
Range Set Operations
import { RangeSet, CIDR, IPv4 } from 'ip-kit';
// Create range sets from CIDRs
const set1 = RangeSet.fromCIDRs(['192.168.1.0/25', '192.168.2.0/24']);
const set2 = RangeSet.fromCIDRs(['192.168.1.128/25', '192.168.3.0/24']);
// Union (combine ranges)
const union = set1.union(set2);
console.log(union.size()); // 512n + 256n = 768n
// Intersection (overlapping ranges)
const intersection = set1.intersect(set2);
console.log(intersection.size()); // 0n (no overlap)
// Subtraction (remove ranges)
const difference = set1.subtract(set2);
console.log(difference.size()); // 512n
// Check containment
console.log(set1.contains(IPv4.parse('192.168.1.50'))); // true
console.log(set1.containsCIDR(CIDR.parse('192.168.1.64/26'))); // true
// Convert to minimal CIDRs
const minimalCIDRs = union.toCIDRs();
console.log(minimalCIDRs.map((c) => c.toString()));
// ['192.168.1.0/24', '192.168.2.0/24', '192.168.3.0/24']
IP Address Allocation
import { Allocator, CIDR, IPv4 } from 'ip-kit';
// Create allocator for a /24 network
const parent = CIDR.parse('192.168.1.0/24');
const allocator = new Allocator(parent);
// Allocate next available IP
const ip1 = allocator.allocateNext();
console.log(ip1?.toString()); // '192.168.1.1'
// Allocate specific IP
const success = allocator.allocateIP(IPv4.parse('192.168.1.10'));
console.log(success); // true
// Allocate CIDR block
const cidrSuccess = allocator.allocateCIDR(CIDR.parse('192.168.1.64/26'));
console.log(cidrSuccess); // true
// Find next available IP
const next = allocator.nextAvailable();
console.log(next?.toString()); // '192.168.1.2'
// Get free blocks
const freeBlocks = allocator.freeBlocks({ minPrefix: 27 });
console.log(freeBlocks.map((b) => b.toString()));
// Check utilization
console.log(`Utilization: ${(allocator.utilization() * 100).toFixed(1)}%`);
// Get available count
console.log(`Available IPs: ${allocator.availableCount()}`);
Longest-Prefix Matching (Routing)
import { RadixTrie, CIDR, IPv4 } from 'ip-kit';
// Create routing table
const routingTable = new RadixTrie<4, string>(4);
// Add routes with associated interface/gateway info
routingTable
.insert(CIDR.parse('0.0.0.0/0'), 'default-gateway')
.insert(CIDR.parse('192.168.0.0/16'), 'lan-interface')
.insert(CIDR.parse('192.168.1.0/24'), 'server-subnet')
.insert(CIDR.parse('192.168.1.128/25'), 'dmz-subnet');
// Find best route for destination IP
const destIP = IPv4.parse('192.168.1.150');
const route = routingTable.longestMatch(destIP);
if (route) {
console.log(`Route: ${route.cidr.toString()}`);
console.log(`Next hop: ${route.value}`);
// Output: Route: 192.168.1.128/25, Next hop: dmz-subnet
}
// Remove a route
routingTable.remove(CIDR.parse('192.168.1.128/25'));
// Get all routes
const allRoutes = routingTable.getCIDRs();
console.log(`Total routes: ${routingTable.size()}`);
Error Handling
import { IPv4, CIDR, ParseError } from 'ip-kit';
try {
const ip = IPv4.parse('256.1.1.1'); // Invalid
} catch (error) {
if (error instanceof ParseError) {
console.log('Parse error:', error.message);
}
}
try {
const cidr = CIDR.parse('192.168.1.0/33'); // Invalid prefix
} catch (error) {
console.log('Invalid CIDR:', error.message);
}
Errors & Types
This library throws a small set of custom errors exported from
src/core/errors.ts
:ParseError
— input parsing failuresInvariantError
— internal invariant violation (logic error)OutOfRangeError
— numeric or prefix out-of-rangeVersionMismatchError
— operations attempted with mixed IP versions
Important: most address/size arithmetic uses
BigInt
. Where counts are small (indexes, array sizes)number
is used, but IP math and sizes returnbigint
.
Test & Development (recommended)
Run these commands from the project root:
# install dependencies (use npm or pnpm as preferred)
npm ci
# typecheck
npm run typecheck
# run tests
npm test
# build
npm run build
Advanced Examples
IPAM (IP Address Management) System
import { Allocator, RangeSet, CIDR, IPv4 } from 'ip-kit';
// Simulate IPAM for a data center
class IPAMSystem {
private allocators: Map<string, Allocator<4>> = new Map();
addSubnet(name: string, cidr: string, takenRanges: string[] = []) {
const parent = CIDR.parse(cidr) as CIDR<4>;
const taken = RangeSet.fromCIDRs(takenRanges);
this.allocators.set(name, new Allocator(parent, taken));
}
allocateIP(subnetName: string): IPv4 | null {
const allocator = this.allocators.get(subnetName);
return allocator?.allocateNext() || null;
}
getUtilization(subnetName: string): number {
const allocator = this.allocators.get(subnetName);
return allocator?.utilization() || 0;
}
findFreeBlocks(subnetName: string, minPrefix = 24) {
const allocator = this.allocators.get(subnetName);
return allocator?.freeBlocks({ minPrefix }) || [];
}
}
// Usage
const ipam = new IPAMSystem();
ipam.addSubnet('web-servers', '10.0.1.0/24', ['10.0.1.1/32', '10.0.1.2/32']);
ipam.addSubnet('database', '10.0.2.0/24');
const webIP = ipam.allocateIP('web-servers');
console.log(`Allocated web server IP: ${webIP?.toString()}`);
console.log(`Web subnet utilization: ${(ipam.getUtilization('web-servers') * 100).toFixed(1)}%`);
const freeBlocks = ipam.findFreeBlocks('database', 25);
console.log(`Available /25 blocks in database subnet: ${freeBlocks.length}`);
Routing Table Implementation
import { RadixTrie, CIDR, IPv4 } from 'ip-kit';
interface RouteInfo {
interface: string;
gateway?: string;
metric: number;
}
class RoutingTable {
private ipv4Routes: RadixTrie<4, RouteInfo> = new RadixTrie(4);
addRoute(cidrStr: string, info: RouteInfo) {
const cidr = CIDR.parse(cidrStr);
if (cidr.version === 4) {
this.ipv4Routes.insert(cidr as CIDR<4>, info);
}
}
lookupRoute(destination: string): RouteInfo | null {
const ip = IPv4.parse(destination);
const result = this.ipv4Routes.longestMatch(ip);
return result?.value || null;
}
getAllRoutes(): Array<{ cidr: string; info: RouteInfo }> {
const routes: Array<{ cidr: string; info: RouteInfo }> = [];
for (const cidr of this.ipv4Routes.getCIDRs()) {
const result = this.ipv4Routes.longestMatch(cidr.network() as IPv4);
if (result?.value) {
routes.push({ cidr: cidr.toString(), info: result.value });
}
}
return routes;
}
}
// Usage
const routing = new RoutingTable();
routing.addRoute('0.0.0.0/0', { interface: 'eth0', gateway: '192.168.1.1', metric: 100 });
routing.addRoute('192.168.1.0/24', { interface: 'eth1', metric: 10 });
routing.addRoute('10.0.0.0/8', { interface: 'eth2', metric: 20 });
const route = routing.lookupRoute('192.168.1.50');
console.log(`Route to 192.168.1.50: ${route?.interface} (metric: ${route?.metric})`);
const allRoutes = routing.getAllRoutes();
console.log('All routes:', allRoutes);
Caveats and Design Decisions
BigInt Usage: All IP math uses BigInt to avoid floating-point precision issues with large IPv6 addresses. See our IP Calculations Guide for detailed explanations.
Lazy Iterators: Methods like
hosts()
,subnets()
, andips()
return generators to handle large ranges efficiently without memory issues.IPv4 /31 and /32 Handling: For point-to-point links (/31) and single-host (/32),
hosts()
includes all addresses by default. Use{ includeEdges: false }
to exclude network/broadcast.IPv6 Edge Inclusion: IPv6 ranges always include network and broadcast addresses in iterations, as there's no traditional broadcast concept.
RFC 5952 Normalization: IPv6 addresses are automatically normalized to the canonical compressed form.
Type Safety: Strict TypeScript with generics ensures version-specific operations are type-checked.
split() materializes results:
CIDR.split(parts)
returns an array of CIDR objects. For largeparts
(especially with IPv6), this can allocate millions of objects and exhaust memory. Prefer iterating the generator returned bysubnets()
for large or unbounded splits.
Development
Prerequisites
- Node.js 18+
- pnpm (recommended) or npm
Setup
git clone https://github.com/h3mantD/ip-kit.git
cd ip-kit
pnpm install
Available Scripts
# Build the library
pnpm build
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Lint code
pnpm lint
# Format code
pnpm format
# Type check
pnpm typecheck
# Development build with watch
pnpm dev
# Run examples
node examples/basic.js
Project Structure
src/
├── core/ # Core utilities
│ ├── bigint.ts # BigInt math helpers
│ ├── errors.ts # Custom error classes
│ ├── normalize.ts # IPv6 normalization
│ └── ptr.ts # Reverse DNS utilities
├── domain/ # Domain models
│ ├── ip.ts # IP address classes
│ ├── cidr.ts # CIDR classes
│ ├── range.ts # IP range classes
│ ├── rangeset.ts # IP range set operations
│ ├── allocator.ts # IP address allocation
│ └── trie.ts # Radix trie for LPM
└── index.ts # Public exports
tests/ # Test files (106+ tests)
├── core/
│ ├── bigint.test.ts
│ └── ...
└── domain/
├── ip.test.ts
├── cidr.test.ts
├── range.test.ts
├── rangeset.test.ts
├── allocator.test.ts
└── trie.test.ts
Contributing
See CONTRIBUTING.md for development guidelines and contribution process.
License
MIT
Roadmap
- Advanced range set operations (union, intersect, subtract) ✅
- IP allocation and free block finding ✅
- Radix trie for longest-prefix matching ✅
- Performance benchmarks
- WASM backend for high-performance operations
- CLI tool for common operations
- ASN/Geo lookups
- Database integration adapters