Class-based IPv4/IPv6 toolkit for JS/TS: CIDR math, ranges, allocator, trie ā bigint + generators.
npm install @h3mantd/ip-kitTypeScript IP Toolkit for IPv4/IPv6 math, CIDR operations, ranges, allocation, and trie lookups.



- 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
``typescript
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 andsplit()
// can allocate millions of CIDR objects for IPv6-sized spaces.
// If you really need all entries in memory, use :`
// const subnets = network.split(26);
// console.log(subnets.length); // 4
> š” See more examples in the examples/ directory!
- Node.js 18 or higher (BigInt support required)
- npm, pnpm or yarn
`bashUsing npm
npm install @h3mantd/ip-kit
$3
`bash
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)
- ā
IPv6 mixed notation (IPv4-mapped/compatible addresses like
::ffff:192.0.2.1)
- ā
RFC 5952 IPv6 normalization and RFC 4291 mixed notation support
- ā
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 (160+ tests)API Reference
$3
`typescript
type IPVersion = 4 | 6;
`$3
#### IPv4
`typescript
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
`typescript
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
`typescript
function ip(input: string | number | bigint | Uint8Array): IPv4 | IPv6;
function cidr(input: string): CIDR<4> | CIDR<6>;
`$3
`typescript
class CIDR {
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): boolean;
*hosts(opts?: { includeEdges?: boolean }): Generator;
*subnets(newPrefix: number): Generator>;
split(parts: number): CIDR[];
move(n: number): CIDR;
toRange(): IPRange;
toPtr(): string[];
toString(): string;
}
`$3
`typescript
class IPRange {
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): boolean;
contains(ip: IP): boolean;
*ips(limit?: number): Generator;
toCIDRs(): CIDR[];
toString(): string;
}
`$3
`typescript
class RangeSet {
// Static methods
static fromCIDRs(cidrs: Array | string>): RangeSet;
static fromRanges(ranges: Array>): RangeSet; // Instance methods
isEmpty(): boolean;
size(): bigint;
union(other: RangeSet): RangeSet;
intersect(other: RangeSet): RangeSet;
subtract(other: RangeSet): RangeSet;
contains(ip: IP): boolean;
containsCIDR(cidr: CIDR): boolean;
*ips(limit?: number): Generator>;
toCIDRs(): CIDR[];
toString(): string;
}
`$3
`typescript
class Allocator {
readonly parent: CIDR;
readonly taken: RangeSet;
readonly version: V; constructor(parent: CIDR, taken?: RangeSet);
// Allocation methods
nextAvailable(from?: IP): IP | null;
allocateNext(): IP | null;
allocateIP(ip: IP): boolean;
allocateCIDR(cidr: CIDR): boolean;
// Query methods
freeBlocks(opts?: { minPrefix?: number; maxResults?: number }): CIDR[];
availableCount(): bigint;
utilization(): number;
}
`$3
`typescript
class RadixTrie {
readonly version: V; constructor(version: V);
// Core methods
insert(cidr: CIDR, value?: T): this;
remove(cidr: CIDR): this;
longestMatch(ip: IP): { cidr: CIDR; value?: T } | null;
// Utility methods
isEmpty(): boolean;
size(): number;
getCIDRs(): CIDR[];
}
`Usage Examples
$3
`typescript
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']
`$3
`typescript
import { IPv6, CIDR } from 'ip-kit';// Parse with compression (RFC 5952 normalization)
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
}
`$3
`typescript
import { IPv6, ip } from 'ip-kit';// Parse IPv4-mapped IPv6 addresses (::ffff:0:0/96)
const mapped1 = ip('::ffff:192.0.2.128');
console.log(mapped1.toString()); // '::ffff:192.0.2.128' (preserves dotted notation)
const mapped2 = ip('::FFFF:192.168.1.1');
console.log(mapped2.toString()); // '::ffff:192.168.1.1' (lowercase per RFC 5952)
// Full form also works
const mapped3 = ip('0:0:0:0:0:ffff:192.168.1.1');
console.log(mapped3.toString()); // '::ffff:192.168.1.1' (compressed)
// Parse IPv4-compatible IPv6 addresses (::/96, deprecated but supported)
const compatible = ip('::192.0.2.1');
console.log(compatible.toString()); // '::192.0.2.1'
// Special cases preserve standard hex notation
const loopback = ip('::1');
console.log(loopback.toString()); // '::1' (not '::0.0.0.1')
const unspecified = ip('::');
console.log(unspecified.toString()); // '::' (not '::0.0.0.0')
// Mixed notation works in parsing, but non-mapped addresses output as hex
const custom = ip('2001:db8:1:1:1:1:192.168.0.10');
console.log(custom.toString()); // '2001:db8:1:1:1:1:c0a8:a' (hex output)
// Validation - all of these throw ParseError
try {
ip('::ffff:256.1.1.1'); // Octet > 255
} catch (e) { console.log(e.message); }
try {
ip('::192.168.1'); // Incomplete IPv4 (only 3 octets)
} catch (e) { console.log(e.message); }
try {
ip('::192.168.01.1'); // Leading zeros not allowed
} catch (e) { console.log(e.message); }
try {
ip(':::192.168.1.1'); // Too many consecutive colons
} catch (e) { console.log(e.message); }
try {
ip('::1::192.168.1.1'); // Multiple :: compressions
} catch (e) { console.log(e.message); }
// Convert between formats
const v4mapped = ip('::ffff:192.0.2.1');
const bytes = v4mapped.toBytes();
// Uint8Array(16) [0,0,0,0,0,0,0,0,0,0,0xff,0xff,192,0,2,1]
const bigint = v4mapped.toBigInt();
// 281473902969345n (0x0000_0000_0000_0000_0000_ffff_c000_0201)
`$3
`typescript
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()));
`$3
`typescript
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']
`$3
`typescript
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()});
`$3
`typescript
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()});
`$3
`typescript
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 failures
- InvariantError ā internal invariant violation (logic error)
- OutOfRangeError ā numeric or prefix out-of-range
- VersionMismatchError ā 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 return bigint.Test & Development (recommended)
Run these commands from the project root:
`bash
install dependencies (use npm or pnpm as preferred)
npm citypecheck
npm run typecheckrun tests
npm testbuild
npm run build
`Advanced Examples
$3
`typescript
import { Allocator, RangeSet, CIDR, IPv4 } from 'ip-kit';// Simulate IPAM for a data center
class IPAMSystem {
private allocators: Map> = 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});
`$3
`typescript
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(), and ips() 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 (lowercase hex, longest zero run compressed with ::, etc.).
- RFC 4291 Mixed Notation: IPv6 addresses with dotted-decimal IPv4 tails are fully supported:
- IPv4-mapped (::ffff:x.x.x.x) - Output preserves mixed notation
- IPv4-compatible (::x.x.x.x) - Output preserves mixed notation (except :: and ::1)
- Other prefixes - Parsed correctly but output as standard hex notation
- Strict validation - Rejects malformed input (invalid octets, multiple ::, etc.)
- 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 large parts (especially with IPv6), this can allocate millions of objects and exhaust memory. Prefer iterating the generator returned by subnets() for large or unbounded splits.Development
$3
- Node.js 18+
- pnpm (recommended) or npm
$3
`bash
git clone https://github.com/h3mantD/ip-kit.git
cd ip-kit
pnpm install
`$3
`bash
Build the library
pnpm buildRun tests
pnpm testRun tests with coverage
pnpm test:coverageLint code
pnpm lintFormat code
pnpm formatType check
pnpm typecheckDevelopment build with watch
pnpm devRun examples
node examples/basic.js
`$3
`
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 exportstests/ # Test files (160+ tests)
āāā core/
ā āāā bigint.test.ts
ā āāā normalize.test.ts
ā āāā ptr.test.ts
ā āāā ...
āāā domain/
āāā ip.test.ts
āāā ipv6-mixed.test.ts
āāā cidr.test.ts
āāā range.test.ts
āāā rangeset.test.ts
āāā allocator.test.ts
āāā trie.test.ts
``See CONTRIBUTING.md for development guidelines and contribution process.
MIT
- [x] Advanced range set operations (union, intersect, subtract) ā
- [x] IP allocation and free block finding ā
- [x] 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