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

@h3mantd/ip-kit

v1.1.0

Published

Class-based IPv4/IPv6 toolkit for JS/TS: CIDR math, ranges, allocator, trie — bigint + generators.

Readme

IP Toolkit

TypeScript IP Toolkit for IPv4/IPv6 math, CIDR operations, ranges, allocation, and trie lookups.

CI npm version license: MIT

Documentation

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)
  • 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

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 (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
}

IPv6 Mixed Notation (IPv4-mapped/compatible)

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)

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 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:

# 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(), 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

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 (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

Contributing

See CONTRIBUTING.md for development guidelines and contribution process.

License

MIT

Roadmap

  • [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