npm package: @ripetchor/utils
Description: Utility functions and types
Author: ripetchor
Latest version: 1.0.1

# Utils

A lightweight TypeScript utility library providing type-safe functions and types for common JavaScript tasks, including type guards, comparators, array manipulation, function optimization, and error handling.

## Usage

### Type Guards

Check the type of a value with type-safe predicates.

```typescript
import { isString, isNumber, isObject } from "@ripetchor/utils";

function processInput(input: unknown) {
  if (isString(input)) {
    return input.toUpperCase(); // TypeScript infers input as string
  }
  if (isNumber(input)) {
    return input * 2; // TypeScript infers input as number
  }
  if (isObject(input)) {
    return Object.keys(input); // TypeScript infers input as object
  }
  return null;
}

console.log(processInput("hello")); // 'HELLO'
console.log(processInput(42)); // 84
console.log(processInput({ key: "value" })); // ['key']
console.log(processInput(null)); // null
```

### Comparators

Sort arrays with customizable comparators for numbers, bigints, dates, and strings.

```typescript
import {
  ascendingString,
  descendingNumber,
  ascendingDate,
} from "@ripetchor/utils";

const strings = ["zebra", "apple", "banana"];
console.log(strings.sort(ascendingString)); // ['apple', 'banana', 'zebra']

const numbers = [10, 2, 5];
console.log(numbers.sort(descendingNumber)); // [10, 5, 2]

const dates = [
  new Date("2023-01-01"),
  new Date("2024-01-01"),
  new Date("2022-01-01"),
];
console.log(dates.sort(ascendingDate)); // [2022-01-01, 2023-01-01, 2024-01-01]
```

### Array Utilities

Manipulate arrays efficiently with `chunk` and `shuffle`.

```typescript
import { chunk, shuffle } from "@ripetchor/utils";

// Chunk: Split array into subarrays of specified size
const numbers = [1, 2, 3, 4, 5, 6];
console.log(chunk(numbers, 2)); // [[1, 2], [3, 4], [5, 6]]
console.log(chunk(numbers, 4)); // [[1, 2, 3, 4], [5, 6]]

// Shuffle: Randomize array order
const arr = [1, 2, 3, 4, 5];
console.log(shuffle(arr)); // e.g., [3, 1, 5, 2, 4] (randomized)
```

### Function Utilities

Optimize function execution with `debounce`, `throttle`, and `memoize`. Each function returns a tuple containing the wrapped function and a control object.

#### Debounce

Delay function execution until after a timeout. Returns `[debouncedFunction, controls]` with methods `cancel`, `flush`, and `pending`.

```typescript
import { debounce } from "@ripetchor/utils";

// Create a debounced function
const [debouncedLog, controls] = debounce(
  (message: string) => console.log(message),
  300,
);

// Queue multiple calls (only the last one executes after 300ms)
debouncedLog("test1");
debouncedLog("test2"); // Only 'test2' logs after 300ms

// Check if a call is pending
console.log(controls.pending()); // true

// Force immediate execution
controls.flush(); // Logs 'test2' immediately
console.log(controls.pending()); // false

// Cancel pending execution
debouncedLog("test3");
controls.cancel(); // No log occurs
console.log(controls.pending()); // false

// Example with context
class Logger {
  message = "Hello";
  log = debounce((value: string) => console.log(this.message, value), 300)[0];
}
const logger = new Logger();
logger.log("World"); // Logs 'Hello World' after 300ms
```

#### Throttle

Limit function execution to once per interval. Returns `[throttledFunction, controls]` with a `cancel` method.

```typescript
import { throttle } from "@ripetchor/utils";

// Create a throttled function
const [throttledLog, controls] = throttle(
  (message: string) => console.log(message),
  1000,
);

// Call multiple times (only one call per 1000ms executes)
throttledLog("test1"); // Logs 'test1'
throttledLog("test2"); // Queued, logs after 1000ms
throttledLog("test3"); // Ignored (already queued)

// Cancel pending call
controls.cancel(); // Cancels 'test2'

// Example with scroll event
window.addEventListener(
  "scroll",
  throttle(() => console.log("Scrolled"), 500)[0],
);
```

#### Memoize

Cache function results based on arguments. Returns `[memoizedFunction, cacheControl]` with methods `clear`, `delete`, `get`, `has`, and `size`.

```typescript
import { memoize } from "@ripetchor/utils";

// Create a memoized function with options
const [expensiveFn, cache] = memoize(
  (n: number) => {
    console.log("Computing...");
    return n * 2;
  },
  {
    ttl: 1000, // Cache expires after 1000ms
    cacheKey: (n) => `key-${n}`, // Custom key
    effects: [(n) => console.log(`Effect for ${n}`)], // Side effect
    effectMode: "compute", // Run effect only on computation
  },
);

// Call memoized function
console.log(expensiveFn(5)); // Computing... Effect for 5, 10
console.log(expensiveFn(5)); // 10 (cached, no effect)
console.log(cache.has("key-5")); // true
console.log(cache.size()); // 1
console.log(cache.get("key-5")); // { timestamp: number, value: 10 }

// Delete specific cache entry
cache.delete("key-5");
console.log(cache.has("key-5")); // false

// Clear entire cache
cache.clear();
console.log(cache.size()); // 0

// Async function example
const [fetchData, fetchCache] = memoize(async (id: number) => ({
  id,
  data: "test",
}));
const result = await fetchData(1); // Fetches and caches
console.log(await fetchData(1)); // Uses cache
```

### Deep Equal

Compare values deeply with `deepEqual`. Supports the following types in detail:

- **Primitives**: Numbers (including special cases like NaN equal to NaN and +0 equal to -0), strings, booleans, null, undefined, symbols (different symbols are not equal), and bigints.
- **Plain Objects**: Compares key-value pairs recursively, including nested objects with identical structures and values.
- **Arrays**: Compares elements recursively by index, including nested arrays and arrays containing NaN or other primitives.
- **Circular References**: Handles self-referential objects or nested structures using WeakMap to prevent infinite recursion, considering structurally identical circular objects as equal.
- **Map**: Compares size, keys (using Map.has for existence), and values recursively with internalEqual for structural equality.
- **Set**: Compares size and checks if all values in one Set exist in the other using Set.has (non-recursive, uses reference equality for objects and strict equality for primitives).
- **Custom Objects with valueOf or toString**: If an object has a custom valueOf or toString method (not inherited from Object.prototype), compares their return values for equality.

Does not support Date, RegExp, ArrayBuffer views, WeakMap, WeakSet, or functions.

```typescript
import { deepEqual } from '@ripetchor/utils';

// Primitives
console.log(deepEqual(42, 42)); // true
console.log(deepEqual('hello', 'hello')); // true
console.log(deepEqual(NaN, NaN)); // true
console.log(deepEqual(-0, 0)); // true
console.log(deepEqual(null, null)); // true
console.log(deepEqual(undefined, null)); // false
console.log(deepEqual(123n, 123n)); // true
console.log(deepEqual(Symbol('id'), Symbol('id'))); // false (different symbols)

// Objects
const obj1 = { a: { b: [1, 2], c: 3 } };
const obj2 = { a: { b: [1, 2], c: 3 } };
console.log(deepEqual(obj1, obj2)); // true
console.log(deepEqual({ a: 1 }, { a: 2 })); // false

// Arrays
console.log(deepEqual([1, 2, [3, 4]], [1, 2, [3, 4]])); // true
console.log(deepEqual([1, 2, NaN], [1, 2, NaN])); // true

// Circular references
const circular1: any = { a: 1 };
circular1.self = circular1;
const circular2: any = { a: 1 };
circular2.self = circular2;
console.log(deepEqual(circular1, circular2)); // true

// Map
console.log(deepEqual(new Map([[1, 'one'], [2, 'two']]), new Map([[1, 'one'], [2, 'two']])); // true
console.log(deepEqual(new Map([[1, 'one']]), new Map([[1, 'two']])); // false

// Set
console.log(deepEqual(new Set([1, 2, 3]), new Set([1, 2, 3]))); // true
console.log(deepEqual(new Set([1, 2]), new Set([1, 3]))); // false

// Custom valueOf/toString
const custom1 = { valueOf: () => 42 };
const custom2 = { valueOf: () => 42 };
console.log(deepEqual(custom1, custom2)); // true
const custom3 = { toString: () => 'test' };
const custom4 = { toString: () => 'test' };
console.log(deepEqual(custom3, custom4)); // true
```

### Error Handling

Normalize errors for consistent handling with `normalizeError`.

```typescript
import { normalizeError } from "@ripetchor/utils";

try {
  throw new Error("Test");
} catch (err) {
  const normalized = normalizeError(err);
  console.log(normalized.message); // 'Test'
}
```

### Types

Enhance type safety with utility types.

```typescript
import type { DeepPartial, DeepReadonly } from "@ripetchor/utils";

interface User {
  name: string;
  age?: number;
  address: { street: string };
}

const partial: DeepPartial<User> = { name: "John" }; // OK
const readonly: DeepReadonly<User> = {
  name: "John",
  age: 30,
  address: { street: "foo" },
};
// readonly.address.street = 'bar'; // Error
```

## API

- **Assertions**: `assert`, `assertNonNullable`
- **Type Guards**: `isString`, `isNumber`, `isBigint`, `isBoolean`, `isObject`, `isArray`, `isFunction`
- **Comparators**: `ascendingNumber`, `descendingNumber`, `ascendingBigint`, `descendingBigint`, `ascendingDate`, `descendingDate`, `ascendingString`, `descendingString`
- **Array Utilities**: `chunk`, `shuffle`
- **Function Utilities**: `debounce`, `throttle`, `memoize`
- **Equality**: `deepEqual`
- **Error Handling**: `normalizeError`
- **Types**: `KeysOf`, `ValueOf`, `Nullable`, `SortDirection`, `DeepPartial`, `DeepRequired`, `Primitive`, `Builtin`, `DeepReadonly`, `NormalizedError`

## License

MIT