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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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)); // null

Comparators

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 300ms

Throttle

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

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.

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'; // 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