ts-data-forge
    Preparing search index...

    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.

    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.

    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.
    npm install ts-data-forge
    

    Or with other package managers:

    # Yarn
    yarn add ts-data-forge

    # pnpm
    pnpm add ts-data-forge

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

    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

    Runtime type checking with TypeScript integration.

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

    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

    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

    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

    Additional helpers for common programming tasks.

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

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

    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.

    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(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(handleStatus('loading') === 'Please wait...');
    assert(handleStatus('success', 'Hello') === 'Data: Hello');
    assert(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(processResult(Result.ok(42)) === 'Success: 42');
    assert(processResult(Result.err('Failed')) === 'Error: Failed');

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

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

    // Basic conversions
    assert(Num.from('123') === 123);
    assert(Number.isNaN(Num.from('abc')));

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

    assert(inRange(5));
    assert(inRange(0)); // (inclusive lower bound)
    assert(!inRange(10)); // (exclusive upper bound)

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

    assert(clamp(150) === 100);
    assert(clamp(-10) === 0);

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

    assert(round2(3.141_59) === 3.14);
    assert(Num.roundAt(3.141_59, 3) === 3.142);
    assert(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(result === 2);
    }

    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(integer === 42);
    assert(unsigned === 42);
    assert(finite === 3.14);
    assert(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(int16 === 1000);
    assert(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(nonZeroInt === 5);
    assert(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(sum === 3000);
    assert(clamped === 32_767);

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

    // Random generation within type constraints
    const randomInt16 = Int16.random(); // Int16 (random value in valid range)
    assert(-32_768 <= randomInt16);
    assert(randomInt16 <= 32_767);

    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(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(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(oldestPerson.value.name === 'Charlie');
    }

    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(originalMap.size === 0);
    assert.deepStrictEqual(mapWithTwo.get('one'), Optional.some(1));

    assert(!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(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(idMapUpdated.size === 9);

    // ISet usage
    const originalSet = ISet.create<number>([]);
    const setWithItems = originalSet.add(1).add(2).add(1); // Duplicate ignored

    assert(originalSet.size === 0); // (unchanged)
    assert(setWithItems.has(1));
    assert(setWithItems.size === 2);

    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(processData({ name: 'Alice' }) === 'Hello, Alice!');
    assert(processData({ age: 30 }) === undefined);
    assert(processData('not an object') === undefined);

    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 * x);

    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]);

    Safely work with readonly types when interfacing with mutable APIs.

    import type * as React from 'react';
    import { castMutable } from 'ts-data-forge';

    // Example: Material-UI Autocomplete
    import { Autocomplete, TextField } from '@mui/material';

    // Immer.js example
    import { produce } from 'immer';

    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']);
    • 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.
    • 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, 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.

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

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

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

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

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

    This project is licensed under the Apache License 2.0.