@ripetchor/utils
v1.0.1
Published
Utility functions and types
Readme
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.
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)); // nullComparators
Sort arrays with customizable comparators for numbers, bigints, dates, and strings.
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.
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.
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 300msThrottle
Limit function execution to once per interval. Returns [throttledFunction, controls] with a cancel method.
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.
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 cacheDeep 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.
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)); // trueError Handling
Normalize errors for consistent handling with normalizeError.
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.
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'; // ErrorAPI
- 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
