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 🙏

© 2025 – Pkg Stats / Ryan Hefner

ts-data-forge

v6.0.0

Published

[![npm version](https://img.shields.io/npm/v/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![npm downloads](https://img.shields.io/npm/dm/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![License](https://img.shields.

Readme

ts-data-forge

npm version npm downloads License codecov

ts-data-forge is a TypeScript utility library that provides type-safe functional programming utilities with zero runtime dependencies. It aims to enhance development robustness, maintainability, and correctness by leveraging TypeScript's powerful type system.

Perfect Companion to ts-type-forge

ts-data-forge is designed as the ideal runtime companion to ts-type-forge, a powerful type utility library. While ts-type-forge provides compile-time type utilities for advanced TypeScript type manipulation, ts-data-forge complements it with runtime utilities that maintain the same level of type safety.

Together, they form a complete TypeScript development toolkit:

  • ts-type-forge: Compile-time type utilities (type manipulation, type inference, advanced type patterns)
  • ts-data-forge: Runtime utilities with strong type safety (type guards, branded types, functional programming utilities)

This synergy enables you to build fully type-safe applications from compile-time to runtime, ensuring type correctness throughout your entire codebase.

Features

This library offers a range of utilities, including:

  • Compile-Time Type Checking: Assert type relationships at compile time with expectType.
  • Immutable Collections: Type-safe and immutable map (IMap), set (ISet) implementations.
  • Array Utilities: A comprehensive suite of functions for array manipulation, creation, transformation, and querying.
  • Number Utilities: Safe and convenient functions for numerical operations, including branded types and range checking.
  • Object Utilities: Helpers for working with objects, such as shallow equality checks.
  • Functional Programming Tools: Utilities like pipe, Optional, and Result to support functional programming patterns.
  • Type Guards: A collection of type guard functions to narrow down types safely.
  • JSON Handling: Type-safe JSON parsing and stringification.
  • And more: Including memoization, casting utilities, and other helpful tools.

Documentation

Installation

npm install ts-data-forge

Or with other package managers:

# Yarn
yarn add ts-data-forge

# pnpm
pnpm add ts-data-forge

TypeScript Configuration

ts-data-forge works best with strict TypeScript settings:

{
    "compilerOptions": {
        "strict": true, // important
        "noUncheckedIndexedAccess": true, // important
        "noPropertyAccessFromIndexSignature": true, // important
        "noFallthroughCasesInSwitch": true,
        "noImplicitOverride": true,
        "noImplicitReturns": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "allowUnreachableCode": false,
        "allowUnusedLabels": false,
        "exactOptionalPropertyTypes": false
    }
}

Core Modules

🎯 Functional Programming

Essential FP utilities for cleaner, more reliable code.

  • Optional - Type-safe null handling
  • Result - Error handling without exceptions
  • Pipe - Function composition utilities
  • match - Pattern matching for TypeScript

🛡️ Type Guards

Runtime type checking with TypeScript integration.

  • Type Checks - isString, isNumber, isNonNullish, etc.
  • Object Guards - isRecord, isNonNullObject, hasKey
  • Utility Guards - isNonEmptyString, isPrimitive

🔢 Number Utilities

Branded number types and safe arithmetic operations.

  • Branded Types - Int, Uint, SafeInt, FiniteNumber
  • Range Types - Int16, Uint32, PositiveInt, etc.
  • Math Utils - Type-safe arithmetic operations

🔧 Array Operations

Type-safe array and tuple utilities with functional programming patterns.

  • Array Utils - Comprehensive array manipulation functions
  • Tuple Utils - Type-safe tuple operations with compile-time guarantees

📦 Collections

Immutable data structures for safer state management.

  • IMap - Immutable Map implementation
  • ISet - Immutable Set implementation

And mutable Queue/Stack implementation

  • Queue - FIFO queue with O(1) operations
  • Stack - LIFO stack implementation

🔍 Other Utilities

Additional helpers for common programming tasks.

  • Type Casting - castMutable, castReadonly
  • Utilities - memoizeFunction, mapNullable, unknownToString
  • Conditionals - ifThen for conditional operations

Usage Examples

Here are some examples of how to use utilities from ts-data-forge:

1. Compile-Time Type Assertions with expectType

The expectType utility allows you to make assertions about types at compile time. This is useful for ensuring type correctness in complex type manipulations or when refactoring.

import { expectType } from 'ts-data-forge';

type User = { id: number; name: string };

type Admin = { id: number; name: string; role: 'admin' };

// Assert that Admin extends User
expectType<Admin, User>('<=');

// Assert that User does not extend Admin
expectType<User, Admin>('!<=');

// Assert exact type equality
expectType<{ x: number }, { x: number }>('=');

// The following would cause a compile-time error:
// expectType<User, Admin>("="); // Error: Type 'User' is not strictly equal to type 'Admin'.

// eslint-disable-next-line @typescript-eslint/no-explicit-any
expectType<User, any>('!='); // Error: Comparisons with `any` are also strictly checked.

2. Functional Programming with Optional, Result, pipe, and match

Handle nullable values and error-prone operations safely.

import { match, Optional, pipe, Result } from 'ts-data-forge';

// Optional for nullable values
const maybeValue = Optional.some(42);

const doubled = Optional.map(maybeValue, (x) => x * 2);

assert.isTrue(Optional.unwrapOr(doubled, 0) === 84);

// Result for error handling
const success = Result.ok(42);

const mapped = Result.map(success, (x) => x * 2);

assert.deepStrictEqual(mapped, Result.ok(84));

// Advanced pipe usage
const processNumber = (input: number): Optional<number> =>
    pipe(input)
        .map((x) => x * 2) // Regular transformation
        .map((x) => x + 10) // Chain transformations
        .map((x) => (x > 50 ? Optional.some(x / 2) : Optional.none)).value; // Get the result

assert.deepStrictEqual(processNumber(30), Optional.some(35));

assert.deepStrictEqual(processNumber(10), Optional.none);

// Pattern matching with match
type Status = 'loading' | 'success' | 'error';

const handleStatus = (status: Status, data?: string): string =>
    match(status, {
        loading: 'Please wait...',
        success: `Data: ${data ?? 'No data'}`,
        error: 'An error occurred',
    });

assert.isTrue(handleStatus('loading') === 'Please wait...');

assert.isTrue(handleStatus('success', 'Hello') === 'Data: Hello');

assert.isTrue(handleStatus('error') === 'An error occurred');

// Pattern matching with Result
const processResult = (result: Result<number, string>): string =>
    Result.isOk(result) ? `Success: ${result.value}` : `Error: ${result.value}`;

assert.isTrue(processResult(Result.ok(42)) === 'Success: 42');

assert.isTrue(processResult(Result.err('Failed')) === 'Error: Failed');

3. Number Utilities with Num and Branded Number Types

The Num object provides safe and convenient functions for numerical operations.

import { Num } from 'ts-data-forge';

// Basic conversions
assert.isTrue(Num.from('123') === 123);

assert.isTrue(Number.isNaN(Num.from('abc')));

// Range checking
const inRange = Num.isInRange(0, 10);

assert.isTrue(inRange(5));

assert.isTrue(inRange(0)); // (inclusive lower bound)

assert.isFalse(inRange(10)); // (exclusive upper bound)

// Clamping values
const clamp = Num.clamp(0, 100);

assert.isTrue(clamp(150) === 100);

assert.isTrue(clamp(-10) === 0);

// Rounding utilities
const round2 = Num.round(2);

assert.isTrue(round2(3.141_59) === 3.14);

assert.isTrue(Num.roundAt(3.141_59, 3) === 3.142);

assert.isTrue(Num.roundToInt(3.7) === 4);

// Type guards
const value = 5; // example value

if (Num.isNonZero(value)) {
    // value is guaranteed to be non-zero
    const result = Num.div(10, value); // Safe division

    assert.isTrue(result === 2);
}

Branded Number Types for Enhanced Type Safety

ts-data-forge provides branded number types that enforce specific constraints at the type level.

import {
    asFiniteNumber,
    asInt,
    asInt16,
    asNonZeroInt,
    asPositiveInt,
    asSafeInt,
    asUint,
    asUint32,
    Int16,
    NonZeroInt,
} from 'ts-data-forge';

// Basic branded types
const integer = asInt(42); // Int - any integer

const unsigned = asUint(42); // Uint - non-negative integer

const finite = asFiniteNumber(3.14); // FiniteNumber - finite floating-point

const safeInt = asSafeInt(42); // SafeInt - integer in safe range

assert.isTrue(integer === 42);

assert.isTrue(unsigned === 42);

assert.isTrue(finite === 3.14);

assert.isTrue(safeInt === 42);

// This line would cause a runtime error:
assert.throw(() => {
    asInt(3.14);
});

// Range-constrained types (16-bit, 32-bit)
const int16 = asInt16(1000); // Int16: [-32768, 32767]

const uint32 = asUint32(3_000_000_000); // Uint32: [0, 4294967295]

assert.isTrue(int16 === 1000);

assert.isTrue(uint32 === 3_000_000_000);

// Non-zero and positive variants
const nonZeroInt = asNonZeroInt(5); // NonZeroInt - excludes zero

const positiveInt = asPositiveInt(10); // PositiveInt - excludes zero and negatives

assert.isTrue(nonZeroInt === 5);

assert.isTrue(positiveInt === 10);

// Type-safe arithmetic with automatic clamping
const sum = Int16.add(int16, asInt16(2000)); // Int16 (3000)

const clamped = Int16.clamp(100_000); // Int16 (32767 - clamped to MAX_VALUE)

assert.isTrue(sum === 3000);

assert.isTrue(clamped === 32_767);

// Safe division with non-zero types
const ratio = NonZeroInt.div(asNonZeroInt(10), nonZeroInt); // No division by zero risk

assert.isTrue(ratio === 2);

// Random generation within type constraints
const randomInt16 = Int16.random(); // Int16 (random value in valid range)

assert.isTrue(-32_768 <= randomInt16);

assert.isTrue(randomInt16 <= 32_767);

4. Array Utilities with Arr

The Arr object provides a rich set of functions for array manipulation.

import { Arr, expectType, Optional } from 'ts-data-forge';

const numbers: readonly number[] = [1, 2, 3, 4, 5, 2, 3];

// Reduction
const sum = Arr.sum(numbers);

assert.isTrue(sum === 20);

// Type-safe length checking
if (Arr.isArrayAtLeastLength(numbers, 2)) {
    // numbers is now guaranteed to have at least 2 elements
    expectType<typeof numbers, readonly [number, number, ...number[]]>('=');

    assert.isTrue(numbers[1] === 2); // Safe access to index 1
}

// Take first n elements
const firstThree = Arr.take(numbers, 3);

assert.deepStrictEqual(firstThree, [1, 2, 3]);

// Remove duplicates
const unique = Arr.uniq(numbers);

assert.deepStrictEqual(unique, [1, 2, 3, 4, 5]);

// Array creation
const zeros: readonly [0, 0, 0, 0, 0] = Arr.zeros(5);

assert.deepStrictEqual(zeros, [0, 0, 0, 0, 0]);

const range: readonly [1, 2, 3] = Arr.range(1, 4);

assert.deepStrictEqual(range, [1, 2, 3]);

const people = [
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 25 },
    { name: 'Charlie', age: 35 },
] as const;

// Find maximum by property
const oldestPerson = Arr.maxBy(people, (person) => person.age);

assert.deepStrictEqual(
    oldestPerson,
    Optional.some({ name: 'Charlie', age: 35 } as const),
);

if (Optional.isSome(oldestPerson)) {
    assert.isTrue(oldestPerson.value.name === 'Charlie');
}

5. Immutable Collections: IMap and ISet

Type-safe, immutable data structures.

import { Arr, IMap, ISet, Optional } from 'ts-data-forge';

// IMap usage - immutable operations
const originalMap = IMap.create<string, number>([]);

const mapWithOne = originalMap.set('one', 1);

const mapWithTwo = mapWithOne.set('two', 2);

// Original map is unchanged
assert.isTrue(originalMap.size === 0);

assert.deepStrictEqual(mapWithTwo.get('one'), Optional.some(1));

assert.isFalse(mapWithTwo.has('three'));

// Using pipe for fluent updates
const sequence = Arr.seq(10); // [0, 1, 2, ..., 9]

const pairs = sequence.map(
    (i) => [i, i.toString()] as readonly [number, string],
);

const skipped = Arr.skip(pairs, 1); // [[1, "1"], ..., [9, "9"]]

const idMap = IMap.create<number, string>(skipped);

assert.isTrue(idMap.size === 9);

// Efficient batch updates with withMutations
const idMapUpdated = idMap.withMutations([
    { type: 'set', key: 99, value: '99' },
    { type: 'update', key: 5, updater: () => 'five' },
    { type: 'delete', key: 4 },
]);

assert.isTrue(idMapUpdated.size === 9);

// ISet usage
const originalSet = ISet.create<number>([]);

const setWithItems = originalSet.add(1).add(2).add(1); // Duplicate ignored

assert.isTrue(originalSet.size === 0); // (unchanged)

assert.isTrue(setWithItems.has(1));

assert.isTrue(setWithItems.size === 2);

6. Type Guards

Safe type narrowing with comprehensive type guards.

import { hasKey, isNonNullObject, isRecord } from 'ts-data-forge';

const processData = (data: unknown): string | undefined => {
    if (
        isRecord(data) && // data is now UnknownRecord (= Readonly<Record<string, unknown>>)
        hasKey(data, 'name') &&
        // data is now ReadonlyRecord<"name", unknown> & UnknownRecord
        typeof data.name === 'string'
    ) {
        return `Hello, ${data.name}!`;
    }

    return undefined;
};

// Non-null object checking
const value: unknown = { key: 'value' };

if (isNonNullObject(value)) {
    // value is guaranteed to be a non-null object
    assert.deepStrictEqual(Object.keys(value), ['key']);
}

// Example usage
assert.isTrue(processData({ name: 'Alice' }) === 'Hello, Alice!');

assert.isTrue(processData({ age: 30 }) === undefined);

assert.isTrue(processData('not an object') === undefined);

7. Iteration with range

Generate ranges for iteration and array creation.

import { range } from 'ts-data-forge';

// Traditional for loop using range
const mut_values: number[] = [];

for (const i of range(0, 5)) {
    mut_values.push(i);
}

assert.deepStrictEqual(mut_values, [0, 1, 2, 3, 4]);

// Create arrays from ranges
const numbers = Array.from(range(1, 4));

const squares = Array.from(range(1, 6), (x) => x ** 2);

assert.deepStrictEqual(numbers, [1, 2, 3]);

assert.deepStrictEqual(squares, [1, 4, 9, 16, 25]);

// Step ranges
const mut_stepValues: number[] = [];

for (const i of range(0, 10, 2)) {
    mut_stepValues.push(i);
}

assert.deepStrictEqual(mut_stepValues, [0, 2, 4, 6, 8]);

8. Mutability Utilities with castMutable

Safely work with readonly types when interfacing with mutable APIs.

import { Autocomplete, TextField } from '@mui/material';
import { produce } from 'immer';
import type * as React from 'react';
import { castMutable } from 'ts-data-forge';

// Example: Material-UI Autocomplete
export const SomeComponent: React.FC = () => (
    <Autocomplete
        options={castMutable(readonlyOptions)}
        renderInput={({
            InputLabelProps,
            InputProps,
            disabled,
            fullWidth,
            id,
            inputProps,
            size,
        }) => (
            <TextField
                slotProps={{
                    inputLabel: InputLabelProps,
                    input: InputProps,
                    htmlInput: inputProps,
                }}
                disabled={disabled}
                fullWidth={fullWidth}
                id={id}
                size={size}
                placeholder="Select an option"
            />
        )}
    />
);

const readonlyOptions: readonly string[] = ['Option 1', 'Option 2', 'Option 3'];

type State = Readonly<{
    items: readonly string[];
}>;

const initialState: State = {
    items: ['item1', 'item2'],
} as const;

const newItems: readonly string[] = ['newItem1', 'newItem2'];

const updatedState = produce(initialState, (draft) => {
    // draft.items expects mutable array, but newItems is readonly
    draft.items = castMutable(newItems); // Safe cast for assignment
});

assert.deepStrictEqual(initialState.items, ['item1', 'item2']);

assert.deepStrictEqual(updatedState.items, ['newItem1', 'newItem2']);

Note: While immer provides castDraft with similar usage, it recursively converts types to mutable, resulting in a more complex (fat) type implementation. When you only need shallow mutability conversion, using castMutable can potentially reduce type-checking computation overhead.

Modules Overview

  • expect-type: Compile-time type assertion utilities for testing and verification.
  • guard: Type guard functions for safe type narrowing (e.g., isNonNullObject, isRecord).
  • functional: Functional programming helpers like Optional, Result, pipe, and match.
  • number: Comprehensive numerical utilities including the Num namespace and an extensive collection of branded number types (Int, Uint, SafeInt, Int16, Int32, Uint16, Uint32, NonZeroInt, PositiveInt, NonNegativeFiniteNumber, etc.) with type-safe arithmetic operations, range checking, and automatic clamping.
  • array: Utilities for working with arrays and tuples, including creation, transformation, and type-safe operations.
  • object: Utilities for working with records/objects (e.g., Obj.shallowEq).
  • json: Type-safe JSON parsing and stringification utilities.
  • collections: Immutable data structures like IMap, ISet, and Queue with full type safety.
  • iterator: Utilities for working with iterators and generators (e.g., range).
  • others: Miscellaneous utilities like castMutable, castReadonly, ifThen, mapNullable, memoizeFunction, tuple, unknownToString.

Key Benefits

  • Type Safety: All utilities are designed with TypeScript's type system in mind, providing compile-time guarantees.
  • Immutability: Data structures and operations promote immutable patterns for safer, more predictable code.
  • Functional Programming: Support for functional programming paradigms with utilities like Optional, Result, and pipe.
  • Zero Runtime Dependencies: The library has no external runtime dependencies except for @sindresorhus/is, keeping your bundle size minimal.
  • Comprehensive Testing: All utilities are thoroughly tested with both runtime and compile-time tests.

Important Notes:

  • This library only supports ESM (ES Modules) and is designed for native ESM environments. CommonJS is not supported.
  • The library uses .mts file extensions and proper ESM exports, making it compatible with modern Node.js ESM resolution and bundlers that support native ESM.
  • This library uses advanced TypeScript features, including branded types for enhanced type safety. Some functions require specific branded types as parameters (such as Uint32 in newArray). The examples above use the small literal numeric values specifically allowed in each function for brevity, but in actual use you should use the provided type conversion functions (such as asUint32) or cast to the appropriate branded type, for example as Uint32.

Removing expectType in Production

Since expectType is only used for compile-time type checking, you should remove these calls in production builds for better performance.

Rollup Configuration

import rollupPluginStrip from '@rollup/plugin-strip';

export default {
    // ... other config
    plugins: [
        // ... other plugins
        rollupPluginStrip({
            functions: ['expectType'],
            include: '**/*.(mts|ts|mjs|js)',
        }),
    ],
};

Vite Configuration

import { defineConfig } from 'vite';

export default defineConfig({
    // ... other config
    build: {
        terserOptions: {
            compress: {
                pure_funcs: ['expectType'],
            },
        },
    },
});

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for detailed guidelines on how to contribute to this project.

License

This project is licensed under the Apache License 2.0.