npm package: @leglaine/node-types
Description: A highly composable runtime type checking library for JavaScript and Node.js.
Author: Lexi Wright
Latest version: 1.0.0

# @leglaine/node-types

[![npm version](https://img.shields.io/npm/v/@leglaine/node-types.svg)](https://www.npmjs.com/package/@leglaine/node-types)
[![npm downloads](https://img.shields.io/npm/dm/@leglaine/node-types.svg)](https://www.npmjs.com/package/@leglaine/node-types)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)

A lightweight, flexible type checking library for Node.js. Supports primitives,
collections, nested checks, custom checks, and both high-performance and
chainable APIs.

## Table of Contents <!-- omit in toc -->

- [Features](#features)
- [Quick Start](#quick-start)
- [Guide](#guide)
    - [Basic Typechecking](#basic-typechecking)
    - [Negation](#negation)
    - [Collections](#collections)
        - [Arrays](#arrays)
        - [Objects](#objects)
        - [Maps](#maps)
        - [Sets](#sets)
    - [Custom Checks](#custom-checks)
    - [Auto-Unwrap \& High-Performance APIs](#auto-unwrap--high-performance-apis)
    - [Prototypes](#prototypes)
- [Performance Notes](#performance-notes)
    - [Benchmarks](#benchmarks)
- [License](#license)

## Features

- Type checks for primitives (`string`, `number`, `boolean`, `null`, `undefined`)
- Type checks for boxed values (`new String`, `new Number`, `new Boolean`)
- Type checks for numeric values (`integer`, `finite`, `positive`, `negative`, `even`, `odd`)
- Type checks for special objects (`symbol`, `date`, `regexp`)
- Type checks for empty collections or objects
- Collection checks (`array`, `object`, `set`, `map`) with single-type, multi-type, and positional support
- Custom type checks via `registerCheck()`
- Negation with `.not`
- Chainable API via `is()`
- Auto-unwrapping helpers (`isX()`) and high-performance raw checks (`isXFast()`)
- Optional prototype extensions for syntactic sugar

## Quick Start

```bash
npm install @leglaine/node-types
```

```js
const { is } = require("@leglaine/node-types");

if (is(10).number) {
    console.log("10 is a number");
}
```

## Guide

### Basic Typechecking

```js
// Primitives
is(5).number; // true
is("hello").string; // true
is(false).boolean; // true
is(null).null; // true
is(undefined).undefined; // true
is(Symbol("foo")).symbol; // true

// Boxed Values
is(new Number(5)).number; // true
is(new String("abc")).string; // true
is(new Boolean(false)).boolean; // true

// Numerics
is(10.5).integer; // false
is(Infinity).finite; // false
is(4).even; // true
is(3).odd; // true
is(-5).negative; // true
is(10).positive; // true

// Empty
is({}).empty; // true

// Special Objects
is(new Date()).date; // true
is(new RegExp("a")).regexp; // true
```

### Negation

```js
is("hello").not.null; // true
is(345).not.undefined; // true
is([1, 2, 3]).not.empty; // true
```

### Collections

#### Arrays

```js
// Untyped
is([1, "a", true]).array(); // true

// Single-type
is([1, 2, 3]).array("number"); // true
is([1, "2", 3]).array("number"); // false

// Multi-type (positional)
is([1, "a"]).array("number", "string"); // true
is([1, "a", true]).array("number", "string"); // false

// Nested
is([
    [1, 2],
    [3, 4],
]).array(array("number")); // true
```

#### Objects

```js
is({ a: 1, b: 2 }).object(); // true
is({ a: 1, b: 2 }).object("number"); // all values are numbers: true
is({ a: 1, b: "x" }).object("number", "string"); // multiple allowed types: true

// Nested
is({ a: [1, 2], b: [3, 4] }).object(array("number")); // true
is({ a: { x: 1 }, b: { y: 2 } }).object(object("number")); // true
```

#### Maps

```js
const m = new Map([
    ["a", 1],
    ["b", "x"],
]);

is(m).map("number", "string"); // true

// Nested
const nestedMap = new Map([
    ["a", new Map([["x", 1]])],
    ["b", new Map([["y", 2]])],
]);
is(nestedMap).map(map("number")); // true
```

#### Sets

```js
is(new Set([1, 2])).set("number"); // true
is(new Set([1, "x"])).set("number", "string"); // true
is(new Set([1, {}])).set("number", "string"); // false

// Nested
is(new Set([new Set([1, 2]), new Set([3, 4])])).set(set("number")); // true
```

### Custom Checks

```js
const { registerCheck, is } = require("@leglaine/node-types");

registerCheck("evenOrGreaterThan10", (n) => n % 2 === 0 || n > 10);
is(12).evenOrGreaterThan10; // true
is(7).evenOrGreaterThan10; // false
```

### Auto-Unwrap & High-Performance APIs

See [performance notes](#performance-notes) for more information.

```js
const { isNumber, isNumberFast } = require("@leglaine/node-types");

isNumber(new Number(5)); // true (auto-unwrap)
isNumberFast(new Number(5)); // false, not unwrapped
isNumberFast(5); // true
```

### Prototypes

Optionally extend built-in prototypes for syntactic sugar.

> ⚠️ Warning: Use with caution. Pollutes global prototypes.

```js
useIsPrototypes();

"hello".is.string; // true
(42).is.number; // true
```

## Performance Notes

- is() proxies introduce minor overhead for composable checks.
- isX() functions skip proxies, but include auto-unwrapping.
- isXFast() functions skip proxies and auto-unwrapping for maximum performance.
- Prefer isXFast for tight loops or high-volume checks.

### Benchmarks

| function                     | ops/sec (approx.) |
| ---------------------------- | ----------------- |
| `is().number` (proxy)        | ~ 1 Million       |
| `isNumber()` (no proxy)      | ~ 10 Million      |
| `isNumberFast()` (no unwrap) | ~ 75 Million      |
| `_.isNumber()` (lodash)      | ~ 3 Million       |

## License

MIT &copy; 2025 Lexi Wright

See [LICENSE](./LICENSE) for more information.