UNPKG

ts-data-forge

Version:

[![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.

1,016 lines 150 kB
import { IMap } from '../collections/index.mjs'; import { Optional, Result } from '../functional/index.mjs'; /** * A comprehensive, immutable utility library for array manipulations in TypeScript. * Provides a wide range of functions for array creation, validation, transformation, * reduction, slicing, set operations, and more, with a focus on type safety and * leveraging TypeScript's type inference capabilities. * All functions operate on `readonly` arrays and return new `readonly` arrays, * ensuring immutability. */ export declare namespace Arr { type ArrayIndex<Ar extends readonly unknown[]> = IsFixedLengthList<Ar> extends true ? IndexOfTuple<Ar> : SizeType.Arr; type ArgArrayIndex<Ar extends readonly unknown[]> = IsFixedLengthList<Ar> extends true ? IndexOfTuple<Ar> : SizeType.ArgArr; type ArgArrayIndexWithNegative<Ar extends readonly unknown[]> = IsFixedLengthList<Ar> extends true ? IndexOfTuple<[...Ar, 0]> | NegativeIndexOfTuple<Ar> : SizeType.ArgArrWithNegative; /** * Returns the size (length) of an array as a type-safe branded integer. * * This function provides the array length with enhanced type safety through branded types: * - For arrays known to be non-empty at compile time: returns `PositiveNumber & SizeType.Arr` * - For general arrays that may be empty: returns `SizeType.Arr` (branded Uint32) * * The returned value is always a non-negative integer that can be safely used for array indexing * and size comparisons. The branded type prevents common integer overflow issues and provides * better type checking than plain numbers. * * @template Ar The exact type of the input array, used for precise return type inference. * @param array The array to measure. Can be any readonly array type. * @returns The length of the array as a branded type: * - `IntersectBrand<PositiveNumber, SizeType.Arr>` for known non-empty arrays * - `SizeType.Arr` for general arrays (branded Uint32, may be 0) * * @example * ```typescript * // Known non-empty arrays get positive branded type * const tuple = [1, 2, 3] as const; * const tupleSize = Arr.size(tuple); * // Type: IntersectBrand<PositiveNumber, SizeType.Arr> * // Value: 3 (branded, guaranteed positive) * * const nonEmpty: NonEmptyArray<string> = ['a', 'b']; * const nonEmptySize = Arr.size(nonEmpty); * // Type: IntersectBrand<PositiveNumber, SizeType.Arr> * // Guaranteed to be > 0 * * // General arrays may be empty, get regular branded type * const generalArray: number[] = [1, 2, 3]; * const generalSize = Arr.size(generalArray); * // Type: SizeType.Arr (branded Uint32) * // May be 0 or positive * * // Empty arrays * const emptyArray = [] as const; * const emptySize = Arr.size(emptyArray); * // Type: SizeType.Arr * // Value: 0 (branded) * * // Runtime arrays with unknown content * const dynamicArray = Array.from({ length: Math.random() * 10 }, (_, i) => i); * const dynamicSize = Arr.size(dynamicArray); * // Type: SizeType.Arr (may be 0) * * // Using size for safe operations * const data = [10, 20, 30]; * const dataSize = Arr.size(data); * * // Safe for array creation * const indices = Arr.seq(dataSize); // Creates [0, 1, 2] * const zeros = Arr.zeros(dataSize); // Creates [0, 0, 0] * * // Functional composition * const arrays = [ * [1, 2], * [3, 4, 5], * [], * [6] * ]; * const sizes = arrays.map(Arr.size); // [2, 3, 0, 1] (all branded) * const totalElements = sizes.reduce(Uint32.add, 0); // 6 * * // Type guards work with size * if (Arr.size(data) > 0) { * // TypeScript knows data is non-empty here * const firstElement = data[0]; // Safe access * } * * // Type inference examples * expectType<typeof tupleSize, IntersectBrand<PositiveNumber, SizeType.Arr>>('='); * expectType<typeof generalSize, SizeType.Arr>('='); * expectType<typeof emptySize, SizeType.Arr>('='); * ``` * * @see {@link length} - Alias for this function * @see {@link isEmpty} for checking if size is 0 * @see {@link isNonEmpty} for checking if size > 0 */ export const size: <Ar extends readonly unknown[]>(array: Ar) => Ar extends NonEmptyArray<unknown> ? IntersectBrand<PositiveNumber, SizeType.Arr> : SizeType.Arr; /** * Type guard that checks if a value is an array, excluding types that cannot be arrays. * This function refines the type by filtering out non-array types from unions. * @template E The input type that may or may not be an array. * @param value The value to check. * @returns `true` if the value is an array, `false` otherwise. * @example * ```ts * function processValue(value: string | number[] | null) { * if (Arr.isArray(value)) { * // value is now typed as number[] * console.log(value.length); * } * } * * Arr.isArray([1, 2, 3]); // true * Arr.isArray("hello"); // false * Arr.isArray(null); // false * ``` */ export const isArray: <E>(value: E) => value is FilterArray<E>; type FilterArray<T> = T extends T ? BoolOr<TypeEq<T, unknown>, TypeEq<T, any>> extends true ? Cast<readonly unknown[], T> : T extends readonly unknown[] ? T : never : never; type Cast<A, B> = A extends B ? A : never; /** * Type guard that checks if an array is empty (has no elements). * * This function serves as both a runtime check and a TypeScript type guard, * narrowing the array type to `readonly []` when the check passes. It's useful * for conditional logic and type-safe handling of potentially empty arrays. * * @template E The type of elements in the array. * @param array The array to check for emptiness. * @returns `true` if the array has length 0, `false` otherwise. * When `true`, TypeScript narrows the type to `readonly []`. * * @example * ```typescript * // Basic emptiness checking * const emptyArray: number[] = []; * const nonEmptyArray = [1, 2, 3]; * * console.log(Arr.isEmpty(emptyArray)); // true * console.log(Arr.isEmpty(nonEmptyArray)); // false * * // Type guard behavior * function processArray(arr: readonly number[]) { * if (Arr.isEmpty(arr)) { * // arr is now typed as readonly [] * console.log('Array is empty'); * // arr[0]; // type error! * return 0; * } * } * * // Early returns * function sumArray(numbers: readonly number[]): number { * if (Arr.isEmpty(numbers)) { * return 0; // Handle empty case early * } * return numbers.reduce((sum, n) => sum + n, 0); * } * * ``` * * @see {@link isNonEmpty} for the opposite check (non-empty arrays) * @see {@link size} for getting the exact length * @see {@link isArrayOfLength} for checking specific lengths */ export const isEmpty: <E>(array: readonly E[]) => array is readonly []; /** * Type guard that checks if an array is non-empty (has at least one element). * * This function serves as both a runtime check and a TypeScript type guard, * narrowing the array type to `NonEmptyArray<E>` when the check passes. This enables * safe access to array elements without undefined checks, as TypeScript knows the array * has at least one element. * * @template E The type of elements in the array. * @param array The array to check for non-emptiness. * @returns `true` if the array has length > 0, `false` otherwise. * When `true`, TypeScript narrows the type to `NonEmptyArray<E>`. * * @example * ```typescript * // Basic non-emptiness checking * const emptyArray: number[] = []; * const nonEmptyArray = [1, 2, 3]; * * console.log(Arr.isNonEmpty(emptyArray)); // false * console.log(Arr.isNonEmpty(nonEmptyArray)); // true * * // Type guard behavior enables safe element access * function getFirstElement(arr: readonly number[]): number | undefined { * if (Arr.isNonEmpty(arr)) { * // arr is now typed as NonEmptyArray<number> * return arr[0]; // Safe - no undefined, TypeScript knows this exists * } * return undefined; * } * * // Safe operations on non-empty arrays * function processData(data: readonly string[]) { * if (!Arr.isNonEmpty(data)) return; // early return pattern * * // This is now safe without undefined checks * const first = data[0]; * * // Can safely use non-empty array methods * const lastElement = Arr.last(data); * * } * * // Filtering and working with arrays * const possiblyEmptyArrays: readonly number[][] = [ * [1, 2, 3], * [], * [4, 5], * [] * ]; * * // Get only non-empty arrays with proper typing * const definitelyNonEmpty = possiblyEmptyArrays.filter(Arr.isNonEmpty); * // Type: NonEmptyArray<number>[] * * // Now safe to access elements * const firstElements = definitelyNonEmpty.map(arr => arr[0]); // [1, 4] * * // Early validation * function calculateAverage(numbers: readonly number[]): number { * if (!Arr.isNonEmpty(numbers)) { * throw new Error('Cannot calculate average of empty array'); * } * * // numbers is now NonEmptyArray<number> * return Arr.sum(numbers) / Arr.size(numbers); * } * * // Functional composition * const arrayGroups = [ * [1, 2], * [], * [3, 4, 5], * [] * ]; * * const nonEmptyGroups = arrayGroups * .filter(Arr.isNonEmpty) // Filter to NonEmptyArray<number>[] * .map(group => group[0]); // Safe access to first element: [1, 3] * * // Combined with other array operations * function processArraySafely<T>( * arr: readonly T[], * processor: (item: T) => string * ): string { * if (Arr.isNonEmpty(arr)) { * return arr.map(processor).join(' -> '); * } * return 'No items to process'; * } * * // Type inference examples * const testArray = [1, 2, 3]; * const isNonEmptyResult = Arr.isNonEmpty(testArray); * * expectType<typeof isNonEmptyResult, boolean>('='); * expectType<Parameters<typeof Arr.isNonEmpty>[0], readonly unknown[]>('='); * * // Type narrowing in conditional * if (Arr.isNonEmpty(testArray)) { * expectType<typeof testArray, NonEmptyArray<number>>('='); * } * ``` * * @see {@link isEmpty} for the opposite check (empty arrays) * @see {@link size} for getting the exact length * @see {@link head} for safely getting the first element * @see {@link last} for safely getting the last element */ export const isNonEmpty: <E>(array: readonly E[]) => array is NonEmptyArray<E>; /** * Checks if an array has a specific length. * @template E The type of elements in the array. * @template N The expected length of the array (must be a number type). * @param array The array to check. * @param len The expected length. * @returns `true` if the array has the specified length, `false` otherwise. * @example * ```ts * const arr: readonly number[] = [1, 2, 3]; * if (Arr.isArrayOfLength(arr, 3)) { * // arr is now typed as readonly [number, number, number] * } * Arr.isArrayOfLength([1, 2], 3); // false * ``` */ export const isArrayOfLength: <E, N extends SizeType.ArgArr>(array: readonly E[], len: N) => array is ArrayOfLength<N, E>; /** * Checks if an array has at least a specific length. * @template E The type of elements in the array. * @template N The minimum expected length of the array (must be a number type). * @param array The array to check. * @param len The minimum expected length. * @returns `true` if the array has at least the specified length, `false` otherwise. * @example * ```ts * const arr: readonly number[] = [1, 2, 3]; * if (Arr.isArrayAtLeastLength(arr, 2)) { * // arr is now typed as readonly [number, number, ...number[]] * } * Arr.isArrayAtLeastLength([1], 2); // false * ``` */ export const isArrayAtLeastLength: <E, N extends SizeType.ArgArr>(array: readonly E[], len: N) => array is ArrayAtLeastLen<N, E>; /** * Checks if an index is within the valid range of an array (i.e., `0 <= index < array.length`). * @template E The type of elements in the array. * @param array The input array. * @param index The index to check. * @returns `true` if the index is within the array bounds, `false` otherwise. * @example * ```ts * Arr.indexIsInRange([10, 20], 0); // true * Arr.indexIsInRange([10, 20], 1); // true * Arr.indexIsInRange([10, 20], 2); // false * Arr.indexIsInRange([10, 20], -1); // false * Arr.indexIsInRange([], 0); // false * ``` */ export const indexIsInRange: <E>(array: readonly E[], index: SizeType.ArgArr) => boolean; /** * Creates an array of zeros with the specified length. * * This function provides compile-time type safety with precise return types: * - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple with exact length * - When `len` is a positive runtime value, returns a `NonEmptyArray<0>` * - Otherwise, returns a `readonly 0[]` that may be empty * * @template N The type of the length parameter. When a `SmallUint` literal is provided, * the return type will be a tuple of exactly that length filled with zeros. * @param len The length of the array to create. Must be a non-negative integer. * @returns An immutable array of zeros. The exact return type depends on the input: * - `ArrayOfLength<N, 0>` when `N` is a `SmallUint` literal * - `NonEmptyArray<0>` when `len` is a positive runtime value * - `readonly 0[]` for general non-negative values * * @example * ```typescript * // Compile-time known lengths produce precise tuple types * const exactLength = Arr.zeros(3); // readonly [0, 0, 0] * const empty = Arr.zeros(0); // readonly [] * * // Runtime positive values produce non-empty arrays * const count = Math.floor(Math.random() * 5) + 1; * const nonEmpty = Arr.zeros(count); // NonEmptyArray<0> * * // General runtime values may be empty * const maybeEmpty = Arr.zeros(Math.floor(Math.random() * 5)); // readonly 0[] * * // Type inference examples * expectType<typeof exactLength, readonly [0, 0, 0]>('='); * expectType<typeof empty, readonly []>('='); * expectType<typeof nonEmpty, NonEmptyArray<0>>('='); * expectType<typeof maybeEmpty, readonly 0[]>('='); * ``` */ export const zeros: <N extends SizeType.ArgArr>(len: N) => N extends SmallUint ? ArrayOfLength<N, 0> : N extends SizeType.ArgArrPositive ? NonEmptyArray<0> : readonly 0[]; /** * Creates a sequence of consecutive integers from 0 to `len-1`. * * This function generates index sequences with precise compile-time typing: * - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple of consecutive integers * - When `len` is a positive runtime value, returns a `NonEmptyArray<SizeType.Arr>` * - Otherwise, returns a `readonly SizeType.Arr[]` that may be empty * * @template N The type of the length parameter. When a `SmallUint` literal is provided, * the return type will be a tuple containing the sequence [0, 1, 2, ..., N-1]. * @param len The length of the sequence to create. Must be a non-negative integer. * @returns An immutable array containing the sequence [0, 1, 2, ..., len-1]. * The exact return type depends on the input: * - `Seq<N>` (precise tuple) when `N` is a `SmallUint` literal * - `NonEmptyArray<SizeType.Arr>` when `len` is a positive runtime value * - `readonly SizeType.Arr[]` for general non-negative values * * @example * ```typescript * // Compile-time known lengths produce precise tuple types * const indices = Arr.seq(4); // readonly [0, 1, 2, 3] * const empty = Arr.seq(0); // readonly [] * const single = Arr.seq(1); // readonly [0] * * // Runtime positive values produce non-empty arrays * const count = Math.floor(Math.random() * 5) + 1; * const nonEmpty = Arr.seq(count); // NonEmptyArray<SizeType.Arr> * * // General runtime values may be empty * const maybeEmpty = Arr.seq(Math.floor(Math.random() * 5)); // readonly SizeType.Arr[] * * // Useful for generating array indices * const data = ['a', 'b', 'c', 'd']; * const indexSequence = Arr.seq(data.length); // [0, 1, 2, 3] * * // Type inference examples * expectType<typeof indices, readonly [0, 1, 2, 3]>('='); * expectType<typeof empty, readonly []>('='); * expectType<typeof single, readonly [0]>('='); * ``` */ export const seq: <N extends SizeType.ArgArr>(len: N) => N extends SmallUint ? Seq<N> : N extends SizeType.ArgArrPositive ? NonEmptyArray<SizeType.Arr> : readonly SizeType.Arr[]; /** * Creates a new array of the specified length, with each position filled with the provided initial value. * * This function provides compile-time type safety with precise return types and performs shallow copying * of the initial value (the same reference is used for all positions): * - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple of exactly that length * - When `len` is a positive runtime value, returns a `NonEmptyArray<V>` * - Otherwise, returns a `readonly V[]` that may be empty * * @template V The type of the initial value. The `const` constraint preserves literal types. * @template N The type of the length parameter when it's a `SmallUint` literal. * @param len The length of the array to create. Must be a non-negative integer. * @param init The value to fill each position with. The same reference is used for all positions. * @returns An immutable array filled with the initial value. The exact return type depends on the length: * - `ArrayOfLength<N, V>` when `len` is a `SmallUint` literal * - `NonEmptyArray<V>` when `len` is a positive runtime value * - `readonly V[]` for general non-negative values * * @example * ```typescript * // Compile-time known lengths produce precise tuple types * const strings = Arr.create(3, 'hello'); // readonly ['hello', 'hello', 'hello'] * const numbers = Arr.create(2, 42); // readonly [42, 42] * const empty = Arr.create(0, 'unused'); // readonly [] * * // Object references are shared (shallow copy behavior) * const obj = { id: 1, name: 'test' }; * const objects = Arr.create(3, obj); // readonly [obj, obj, obj] * objects[0] === objects[1]; // true - same reference * objects[0].id = 999; // Mutates the shared object * console.log(objects[1].id); // 999 - all positions affected * * // Runtime positive values produce non-empty arrays * const count = Math.floor(Math.random() * 5) + 1; * const nonEmpty = Arr.create(count, 'item'); // NonEmptyArray<string> * * // Literal type preservation with const assertion * const literals = Arr.create(2, 'success' as const); // readonly ['success', 'success'] * * // Type inference examples * expectType<typeof strings, readonly ['hello', 'hello', 'hello']>('='); * expectType<typeof numbers, readonly [42, 42]>('='); * expectType<typeof empty, readonly []>('='); * ``` * * @see {@link zeros} for creating arrays filled with zeros * @see {@link seq} for creating sequences of consecutive integers */ export const create: <const V, N extends SizeType.ArgArr>(len: N, init: V) => N extends SmallUint ? ArrayOfLength<N, V> : N extends SizeType.ArgArrPositive ? NonEmptyArray<V> : readonly V[]; /** * Creates an array from a generator function. * * This utility function provides enhanced type safety by constraining the generator function * to prevent incorrect return values. The generator can only yield values of type T and * must return void, which helps catch common mistakes like returning values instead of yielding. * * @template T - The type of elements in the generated array * @param generatorFn - A function that returns a generator yielding elements of type T * @returns A readonly array containing all yielded values from the generator * * @example * ```typescript * const nums:readonly number[] = Arr.generate<number>(function* () { * yield 1; * yield* [2, 3]; * }); * * assert.deepStrictEqual(nums, [1, 2, 3]); * * // Type safety - prevents incorrect returns: * const nums2 = Arr.generate<number>(function* () { * yield 1; * if (someCondition) { * return; // OK - returning is allowed, but must be void * } * yield* [2, 3]; * // return 1; // NG - TypeScript error, cannot return T * }); * ``` */ export const generate: <T>(generatorFn: () => Generator<T, void, unknown>) => readonly T[]; /** * Creates a shallow copy of an array, preserving the exact type signature. * * This function creates a new array with the same elements as the input, but with a new array reference. * Object references within the array are preserved (shallow copy), and the readonly/mutable status * of the array type is maintained. * * @template Ar The exact type of the input array, preserving tuple types, readonly status, and element types. * @param array The array to copy. Can be any array type: mutable, readonly, tuple, or general array. * @returns A new array that is a shallow copy of the input. The return type exactly matches the input type, * preserving readonly status, tuple structure, and element types. * * @example * ```typescript * // Mutable arrays remain mutable * const mutableOriginal = [1, 2, 3]; * const mutableCopy = Arr.copy(mutableOriginal); // number[] * mutableCopy[0] = 999; // OK - still mutable * mutableOriginal[0]; // 1 - original unchanged * * // Readonly arrays remain readonly * const readonlyOriginal = [1, 2, 3] as const; * const readonlyCopy = Arr.copy(readonlyOriginal); // readonly [1, 2, 3] * // readonlyCopy[0] = 999; // Error - readonly array * * // Tuple types are preserved * const tupleOriginal: [string, number, boolean] = ['hello', 42, true]; * const tupleCopy = Arr.copy(tupleOriginal); // [string, number, boolean] * * // Shallow copy behavior with objects * const objectArray = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]; * const objectCopy = Arr.copy(objectArray); * objectCopy[0].name = 'Charlie'; // Mutates the shared object reference * console.log(objectArray[0].name); // 'Charlie' - original affected * objectCopy.push({ id: 3, name: 'Dave' }); // Array structure changes don't affect original * console.log(objectArray.length); // 2 - original array length unchanged * * // Empty arrays * const emptyArray: number[] = []; * const emptyCopy = Arr.copy(emptyArray); // number[] * const emptyTuple = [] as const; * const emptyTupleCopy = Arr.copy(emptyTuple); // readonly [] * * // Type inference examples * expectType<typeof mutableCopy, number[]>('='); * expectType<typeof readonlyCopy, readonly [1, 2, 3]>('='); * expectType<typeof tupleCopy, [string, number, boolean]>('='); * ``` * * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice | Array.prototype.slice} * The underlying implementation uses `slice()` for efficient shallow copying */ export const copy: <Ar extends readonly unknown[]>(array: Ar) => Ar; /** * @internal * Helper type for `range` function to represent a sequence of numbers up to N-1. * `LEQ[N]` would be `0 | 1 | ... | N-1`. */ type LT = Readonly<{ [N in SmallUint]: Index<N>; }>; /** * @internal * This type is used to avoid incorrect type calculation results for unions with `Seq`. * It computes the type of an array generated by `Arr.range(S, E)`. * If `S` or `E` is a union type, it falls back to a more general `readonly number[]` type * to prevent overly complex or incorrect tuple/union types. * Otherwise, it computes a precise tuple type like `readonly [S, S+1, ..., E-1]`. * @template S The start of the range (inclusive), constrained to `SmallUint`. * @template E The end of the range (exclusive), constrained to `SmallUint`. */ type RangeList<S extends SmallUint, E extends SmallUint> = BoolOr<IsUnion<S>, IsUnion<E>> extends true ? readonly RelaxedExclude<LT[E], LT[Min<S>]>[] : List.Skip<S, Seq<E>>; /** * Creates an array of numbers within a specified range with optional step increment. * * This function generates arithmetic sequences with advanced compile-time type inference: * - When `start` and `end` are {@link SmallUint} literals and `step` is 1 (or omitted), returns a precise tuple type * - When parameters are runtime values, returns appropriate array types based on sign constraints * - Empty arrays are returned for invalid ranges (e.g., start ≥ end with positive step) * - Never throws exceptions - invalid parameters result in empty arrays * * **SmallUint Constraint:** The {@link SmallUint} constraint (0-255) enables precise tuple type inference * for compile-time known ranges. This allows TypeScript to compute exact tuple types like `readonly [1, 2, 3, 4]` * instead of generic `readonly number[]`. * * **Type Inference Behavior:** * - Literal {@link SmallUint} values with step=1 → precise tuple type (`RangeList<S, E>`) * - Non-negative parameters → `readonly SafeUint[]` * - Mixed signs or negative parameters → `readonly SafeInt[]` * - Runtime values → lose precise typing but maintain safety * * @template S The type of the start value. When a {@link SmallUint} literal (0-255), enables precise tuple typing. * @template E The type of the end value. When a {@link SmallUint} literal (0-255), enables precise tuple typing. * @param start The start of the range (inclusive). Must be a safe integer. Supports: * - **Literal {@link SmallUint}:** Enables precise tuple types (0-255) * - **Runtime {@link SafeInt}:** Fallback to general array types * - **Negative values:** Supported for countdown sequences * @param end The end of the range (exclusive). Must be a safe integer. Supports: * - **Literal {@link SmallUint}:** Enables precise tuple types (0-255) * - **Runtime {@link SafeInt}:** Fallback to general array types * - **Equal to start:** Results in empty array * @param step The step increment (default: 1). Must be a non-zero safe integer. * - **Positive step:** generates increasing sequence from start to end * - **Negative step:** generates decreasing sequence from start to end * - **Zero step:** Not allowed (branded type prevents this) * @returns An immutable array containing the arithmetic sequence. Return type depends on parameters: * - `RangeList<S, E>` (precise tuple like `readonly [1, 2, 3, 4]`) when `S` and `E` are {@link SmallUint} literals and step is 1 * - `readonly SafeUint[]` when all parameters are non-negative * - `readonly SafeInt[]` for general integer ranges including negative values * * @example * ```typescript * // Compile-time known ranges with step=1 produce precise tuple types * const range1to4 = Arr.range(1, 5); // readonly [1, 2, 3, 4] * const range0to2 = Arr.range(0, 3); // readonly [0, 1, 2] * const emptyRange = Arr.range(5, 5); // readonly [] * const reverseEmpty = Arr.range(5, 1); // readonly [] (invalid with positive step) * * // SmallUint constraint examples (0-255 for precise typing) * const small = Arr.range(0, 10); // readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * const maxSmall = Arr.range(250, 255); // readonly [250, 251, 252, 253, 254] * const beyondSmall = Arr.range(0, 300); // readonly SafeUint[] (loses precision) * * // Custom step increments * const evens = Arr.range(0, 10, 2); // readonly SafeUint[] -> [0, 2, 4, 6, 8] * const odds = Arr.range(1, 10, 2); // readonly SafeUint[] -> [1, 3, 5, 7, 9] * const countdown = Arr.range(5, 0, -1); // readonly SafeInt[] -> [5, 4, 3, 2, 1] * const bigStep = Arr.range(0, 20, 5); // readonly SafeUint[] -> [0, 5, 10, 15] * * // Edge cases that return empty arrays * const singleElement = Arr.range(3, 4); // readonly [3] * const invalidRange = Arr.range(10, 5, 2); // readonly [] (start > end with positive step) * const invalidReverse = Arr.range(1, 10, -1); // readonly [] (start < end with negative step) * const zeroRange = Arr.range(42, 42); // readonly [] (start equals end) * * // Runtime ranges lose precise typing but maintain safety * const dynamicStart = Math.floor(Math.random() * 10) as SafeInt; * const dynamicEnd = (dynamicStart + 5) as SafeInt; * const dynamicRange = Arr.range(dynamicStart, dynamicEnd); // readonly SafeInt[] * * // Negative numbers and mixed signs * const negativeRange = Arr.range(-5, 5); // readonly SafeInt[] -> [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] * const negativeCountdown = Arr.range(0, -5, -1); // readonly SafeInt[] -> [0, -1, -2, -3, -4] * * // Useful for generating index ranges and iteration * const indices = Arr.range(0, 10); // readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * const reversedIndices = Arr.range(9, -1, -1); // readonly SafeInt[] -> [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] * * // Functional programming patterns * const squares = Arr.range(1, 6).map(x => x * x); // [1, 4, 9, 16, 25] * const fibonacci = Arr.range(0, 10).reduce((acc, _, i) => { * if (i <= 1) return [...acc, i]; * return [...acc, acc[i-1] + acc[i-2]]; * }, [] as number[]); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] * * // Type inference examples showing precise vs general types * expectType<typeof range1to4, readonly [1, 2, 3, 4]>('='); // Precise tuple * expectType<typeof emptyRange, readonly []>('='); // Precise empty tuple * expectType<typeof evens, readonly SafeUint[]>('='); // General positive array * expectType<typeof countdown, readonly SafeInt[]>('='); // General integer array * expectType<typeof negativeRange, readonly SafeInt[]>('='); // General integer array * expectType<typeof small, readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>('='); // Precise tuple * expectType<typeof beyondSmall, readonly SafeUint[]>('='); // General array (beyond SmallUint) * ``` * * @throws Never throws - invalid ranges simply return empty arrays * @see {@link seq} for creating sequences starting from 0 * @see {@link SmallUint} for understanding the constraint that enables precise typing * @see {@link SafeInt} and {@link SafeUint} for the safe integer types used */ export function range<S extends SmallUint, E extends SmallUint>(start: S, end: E, step?: 1): RangeList<S, E>; export function range(start: SafeUintWithSmallInt, end: SafeUintWithSmallInt, step?: PositiveSafeIntWithSmallInt): readonly SafeUint[]; export function range(start: SafeIntWithSmallInt, end: SafeIntWithSmallInt, step?: NonZeroSafeIntWithSmallInt): readonly SafeInt[]; /** * Safely retrieves an element at a given index from an array, returning an {@link Optional}. * * This function provides type-safe array access with support for negative indexing * (e.g., -1 for the last element). Unlike direct array access which can return * `undefined` for out-of-bounds indices, this function always returns a well-typed * {@link Optional} that explicitly represents the possibility of absence. * * **Negative Indexing:** Negative indices count from the end of the array: * - `-1` refers to the last element * - `-2` refers to the second-to-last element, etc. * * **Curried Usage:** This function supports currying - when called with only an index, it returns * a function that can be applied to arrays, making it ideal for use in pipe operations. * * **Optional Return Type:** The return type is always {@link Optional}<E> which provides: * - Type-safe access without `undefined` in your business logic * - Explicit handling of \"not found\" cases * - Composable error handling with {@link Optional} utilities * * @template E The type of elements in the array. * @param array The array to access (when using direct call syntax). * @param index The index to access. Must be a branded `SizeType.ArgArr` (safe integer). Can be: * - **Positive integer:** 0-based index from the start (0, 1, 2, ...) * - **Negative integer:** index from the end (-1 is last element, -2 is second-to-last, etc.) * - **Out of bounds:** any index beyond array bounds returns {@link Optional.None} * @returns An {@link Optional}<E> containing: * - {@link Optional.Some}<E> with the element if the index is valid * - {@link Optional.None} if the index is out of bounds (including empty arrays) * * @example * ```typescript * // Direct usage with positive indices * const fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry']; * const first = Arr.at(fruits, 0); // Optional.Some('apple') * const third = Arr.at(fruits, 2); // Optional.Some('cherry') * const outOfBounds = Arr.at(fruits, 10); // Optional.None * * // Negative indexing (accessing from the end) * const last = Arr.at(fruits, -1); // Optional.Some('elderberry') * const secondLast = Arr.at(fruits, -2); // Optional.Some('date') * const negativeOutOfBounds = Arr.at(fruits, -10); // Optional.None * * // Edge cases * const emptyResult = Arr.at([], 0); // Optional.None * const negativeOnEmpty = Arr.at([], -1); // Optional.None * const singleElement = Arr.at(['only'], 0); // Optional.Some('only') * const singleNegative = Arr.at(['only'], -1); // Optional.Some('only') * * // Safe access pattern with type-safe unwrapping * const maybeElement = Arr.at(fruits, 2); * if (Optional.isSome(maybeElement)) { * console.log(`Found: ${maybeElement.value}`); // Type-safe access, no undefined * } else { * console.log('Index out of bounds'); * } * * // Alternative unwrapping with default * const elementOrDefault = Optional.unwrapOr(Arr.at(fruits, 100), 'not found'); * console.log(elementOrDefault); // 'not found' * * // Curried usage for functional composition * const getSecondElement = Arr.at(1); * const getLastElement = Arr.at(-1); * const getMiddleElement = Arr.at(2); * * const nestedArrays = [ * [10, 20, 30, 40], * [50, 60], * [70] * ]; * const secondElements = nestedArrays.map(getSecondElement); * // [Optional.Some(20), Optional.None, Optional.None] * * const lastElements = nestedArrays.map(getLastElement); * // [Optional.Some(40), Optional.Some(60), Optional.Some(70)] * * // Pipe composition for data processing * const processArray = (arr: readonly string[]) => pipe(arr) * .map(getSecondElement) * .map(opt => Optional.map(opt, s => s.toUpperCase())) * .map(opt => Optional.unwrapOr(opt, 'MISSING')) * .value; * * console.log(processArray(['a', 'b', 'c'])); // 'B' * console.log(processArray(['x'])); // 'MISSING' * * // Advanced curried usage with transformation pipelines * const extractAndProcess = pipe([ * ['hello', 'world', 'typescript'], * ['functional', 'programming'], * ['type', 'safety', 'matters', 'most'] * ]) * .map(arr => arr.map(getSecondElement)) * .map(opts => opts.map(opt => Optional.unwrapOr(opt, '[missing]'))) * .value; * // [['world'], ['[missing]'], ['safety']] * * // Type inference examples * expectType<typeof first, Optional<string>>('='); * expectType<typeof getSecondElement, <T>(array: readonly T[]) => Optional<T>>('='); * expectType<typeof negativeOutOfBounds, Optional<string>>('='); * ``` * * @see {@link head} for getting the first element specifically * @see {@link last} for getting the last element specifically * @see {@link Optional} for working with the returned Optional values * @see {@link Optional.unwrapOr} for safe unwrapping with defaults * @see {@link Optional.map} for transforming Optional values */ export function at<Ar extends readonly unknown[]>(array: Ar, index: ArgArrayIndexWithNegative<Ar>): Optional<Ar[number]>; export function at(index: SizeType.ArgArrWithNegative): <E>(array: readonly E[]) => Optional<E>; /** * Returns the first element of an array wrapped in an Optional. * * This function provides type-safe access to the first element with precise return types: * - For empty arrays: returns `Optional.None` * - For tuples with known first element: returns `Optional.Some<FirstElementType>` * - For non-empty arrays: returns `Optional.Some<ElementType>` * - For general arrays: returns `Optional<ElementType>` * * The function leverages TypeScript's type system to provide the most precise return type * based on the input array type, making it safer than direct indexing. * * @template E The type of elements in the array. * @param array The array to get the first element from. * @returns An Optional containing the first element: * - `Optional.None` if the array is empty * - `Optional.Some<E>` containing the first element if the array is non-empty * * @example * ```typescript * // Empty array - precise None type * const emptyResult = Arr.head([]); // Optional.None * console.log(Optional.isNone(emptyResult)); // true * * // Tuple with known structure - precise Some type * const tupleResult = Arr.head(['first', 'second', 'third'] as const); * // Type: Optional.Some<'first'> * if (Optional.isSome(tupleResult)) { * console.log(tupleResult.value); // 'first' - TypeScript knows exact type * } * * // Non-empty array - guaranteed Some type * const nonEmpty: NonEmptyArray<number> = [10, 20, 30] as NonEmptyArray<number>; * const guaranteedResult = Arr.head(nonEmpty); // Optional.Some<number> * // No need to check - always Some for NonEmptyArray * * // General array - may be Some or None * const generalArray: number[] = [1, 2, 3]; * const maybeResult = Arr.head(generalArray); // Optional<number> * if (Optional.isSome(maybeResult)) { * console.log(`First element: ${maybeResult.value}`); * } else { * console.log('Array is empty'); * } * * // Working with different types * const strings = ['hello', 'world']; * const firstString = Arr.head(strings); // Optional<string> * * const objects = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]; * const firstObject = Arr.head(objects); // Optional<{id: number, name: string}> * * // Functional composition * const getFirstElements = (arrays: readonly number[][]) => * arrays.map(Arr.head).filter(Optional.isSome); * * const nestedArrays = [[1, 2], [3, 4], [], [5]]; * const firstElements = getFirstElements(nestedArrays); * // [Optional.Some(1), Optional.Some(3), Optional.Some(5)] * * // Type inference examples * expectType<typeof emptyResult, Optional.None>('='); * expectType<typeof tupleResult, Optional.Some<'first'>>('='); * expectType<typeof guaranteedResult, Optional.Some<number>>('='); * expectType<typeof maybeResult, Optional<number>>('='); * ``` * * @see {@link last} for getting the last element * @see {@link at} for accessing elements at specific indices * @see {@link tail} for getting all elements except the first */ export const head: <Ar extends readonly unknown[]>(array: Ar) => Ar extends readonly [] ? Optional.None : Ar extends readonly [infer E, ...unknown[]] ? Optional.Some<E> : Ar extends NonEmptyArray<infer E> ? Optional.Some<E> : Optional<Ar[number]>; /** * Returns the last element of an array wrapped in an Optional. * * This function provides type-safe access to the last element with precise return types: * - For empty arrays: returns `Optional.None` * - For tuples with known last element: returns `Optional.Some<LastElementType>` * - For non-empty arrays: returns `Optional.Some<ElementType>` * - For general arrays: returns `Optional<ElementType>` * * The function leverages TypeScript's type system to provide the most precise return type * based on the input array type, making it safer than direct indexing. * * @template E The type of elements in the array. * @param array The array to get the last element from. * @returns An Optional containing the last element: * - `Optional.None` if the array is empty * - `Optional.Some<E>` containing the last element if the array is non-empty * * @example * ```typescript * // Empty array - precise None type * const emptyResult = Arr.last([]); // Optional.None * console.log(Optional.isNone(emptyResult)); // true * * // Tuple with known structure - precise Some type * const tupleResult = Arr.last(['first', 'middle', 'last'] as const); * // Type: Optional.Some<'last'> * if (Optional.isSome(tupleResult)) { * console.log(tupleResult.value); // 'last' - TypeScript knows exact type * } * * // Non-empty array - guaranteed Some type * const nonEmpty: NonEmptyArray<number> = [10, 20, 30] as NonEmptyArray<number>; * const guaranteedResult = Arr.last(nonEmpty); // Optional.Some<number> * // No need to check - always Some for NonEmptyArray * * // General array - may be Some or None * const generalArray: number[] = [1, 2, 3]; * const maybeResult = Arr.last(generalArray); // Optional<number> * if (Optional.isSome(maybeResult)) { * console.log(`Last element: ${maybeResult.value}`); * } else { * console.log('Array is empty'); * } * * // Working with different types * const strings = ['hello', 'world', 'example']; * const lastString = Arr.last(strings); // Optional<string> * * const coordinates = [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}]; * const lastCoordinate = Arr.last(coordinates); // Optional<{x: number, y: number}> * * // Single element arrays * const single = [42]; * const singleResult = Arr.last(single); // Optional<number> containing 42 * * // Functional composition with arrays of arrays * const getLastElements = (arrays: readonly string[][]) => * arrays.map(Arr.last).filter(Optional.isSome); * * const nestedArrays = [['a', 'b'], ['c'], [], ['d', 'e', 'f']]; * const lastElements = getLastElements(nestedArrays); * // [Optional.Some('b'), Optional.Some('c'), Optional.Some('f')] * * // Common pattern: get last element or default * const data = [10, 20, 30]; * const lastOrDefault = Optional.unwrapOr(Arr.last(data), 0); // 30 * const emptyLastOrDefault = Optional.unwrapOr(Arr.last([]), 0); // 0 * * // Type inference examples * expectType<typeof emptyResult, Optional.None>('='); * expectType<typeof tupleResult, Optional.Some<'last'>>('='); * expectType<typeof guaranteedResult, Optional.Some<number>>('='); * expectType<typeof maybeResult, Optional<number>>('='); * ``` * * @see {@link head} for getting the first element * @see {@link at} for accessing elements at specific indices with negative indexing support * @see {@link butLast} for getting all elements except the last */ export const last: <Ar extends readonly unknown[]>(array: Ar) => Ar extends readonly [] ? Optional.None : Ar extends readonly [...unknown[], infer E] ? Optional.Some<E> : Ar extends NonEmptyArray<infer E> ? Optional.Some<E> : Optional<Ar[number]>; /** * Slices an array with automatically clamped start and end indices for safe bounds handling. * * This function provides a safer alternative to `Array.slice()` by automatically clamping * the start and end indices to valid bounds, preventing out-of-bounds access and ensuring * consistent behavior regardless of input values. * * **Clamping Behavior:** * - `start` is clamped to `[0, array.length]` * - `end` is clamped to `[clampedStart, array.length]` (ensuring end ≥ start) * - Invalid ranges (start > end after clamping) return empty arrays * - Negative indices are clamped to 0, large indices are clamped to array.length * * **Curried Usage:** This function supports currying - when called with only start and end * indices, it returns a function that can be applied to arrays. * * @template E The type of elements in the array. * @param array The array to slice (when using direct call syntax). * @param start The start index for the slice (inclusive). Will be clamped to valid bounds. * @param end The end index for the slice (exclusive). Will be clamped to valid bounds. * @returns A new immutable array containing the sliced elements. Always returns a valid array, * never throws for out-of-bounds indices. * * @example * ```typescript * const data = [10, 20, 30, 40, 50]; * * // Normal slicing * const middle = Arr.sliceClamped(data, 1, 4); // [20, 30, 40] * const beginning = Arr.sliceClamped(data, 0, 2); // [10, 20] * const end = Arr.sliceClamped(data, 3, 5); // [40, 50] * * // Automatic clamping for out-of-bounds indices * const clampedStart = Arr.sliceClamped(data, -10, 3); // [10, 20, 30] (start clamped to 0) * const clampedEnd = Arr.sliceClamped(data, 2, 100); // [30, 40, 50] (end clamped to length) * const bothClamped = Arr.sliceClamped(data, -5, 100); // [10, 20, 30, 40, 50] (entire array) * * // Invalid ranges become empty arrays * const emptyReversed = Arr.sliceClamped(data, 4, 1); // [] (start > end after clamping) * const emptyAtEnd = Arr.sliceClamped(data, 5, 10); // [] (start at end of array) * * // Edge cases * const emptyArray = Arr.sliceClamped([], 0, 5); // [] (empty input) * const singleElement = Arr.sliceClamped([42], 0, 1); // [42] * const fullCopy = Arr.sliceClamped(data, 0, data.length); // [10, 20, 30, 40, 50] * * // Curried usage for functional composition * const takeFirst3 = Arr.sliceClamped(0, 3); * const getMiddle2 = Arr.sliceClamped(1, 3); * * const arrays = [ * [1, 2, 3, 4, 5], * [10, 20], * [100, 200, 300, 400, 500, 600] * ]; * * const first3Elements = arrays.map(takeFirst3); * // [[1, 2, 3], [10, 20], [100, 200, 300]] * * const middle2Elements = arrays.map(getMiddle2); * // [[2, 3], [20], [200, 300]] * * // Pipe composition * const result = pipe([1, 2, 3, 4, 5, 6]) * .map(takeFirst3) * .map(Arr.sum) * .value; // 6 (sum of [1, 2, 3]) * * // Comparison with regular Array.slice (which can throw or behave unexpectedly) * try { * // Regular slice with out-of-bounds - works but may be unintuitive * const regularSlice = data.slice(-10, 100); // [10, 20, 30, 40, 50] * // sliceClamped provides same safe behavior explicitly * const clampedSlice = Arr.sliceClamped(data, -10, 100); // [10, 20, 30, 40, 50] * } catch (error) { * // sliceClamped never throws * } * ``` * * @see {@link take} for taking the first N elements * @see {@link skip} for skipping the first N elements * @see {@link takeLast} for taking the last N elements */ export function sliceClamped<Ar extends readonly unknown[]>(array: Ar, start: ArgArrayIndexWithNegative<Ar>, end: ArgArrayIndexWithNegative<Ar>): readonly Ar[number][]; export function sliceClamped(start: SizeType.ArgArrWithNegative, end: SizeType.ArgArrWithNegative): <E>(array: readonly E[]) => readonly E[]; /** * Returns all elements of an array except the first one. * @template E The type of the array (can be a tuple for more precise typing). * @param array The input array. * @returns A new array containing all elements except the first. The type is inferred as `List.Tail<T>`. * @example * ```ts * Arr.tail([1, 2, 3] as cons