@catbee/utils
Version:
A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.
316 lines (313 loc) • 13.5 kB
TypeScript
/*
* The MIT License
*
* Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Delays execution for a specified number of milliseconds.
*
* @param {number} ms - The number of milliseconds to sleep.
* @returns {Promise<void>} A Promise that resolves after the given time.
*/
declare function sleep(ms: number): Promise<void>;
/**
* Creates a debounced version of a function that delays its execution.
* Provides `.cancel()` and `.flush()` methods.
*
* @template T
* @param {T} fn - The function to debounce.
* @param {number} delay - Delay in milliseconds.
* @returns {T & { cancel: () => void; flush: () => void }} A debounced function.
*/
declare function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): {
(...args: Parameters<T>): void;
cancel(): void;
flush(): void;
} & {
cancel: () => void;
flush: () => void;
};
/**
* Creates a throttled version of a function that limits its execution rate.
* Allows control over leading/trailing invocation.
*
* @template T
* @param {T} fn - The function to throttle.
* @param {number} limit - Minimum time between calls in milliseconds.
* @param {{ leading?: boolean, trailing?: boolean }} [opts] - Options for leading/trailing edge throttling.
* @returns {(...args: Parameters<T>) => void} A throttled function.
*/
declare function throttle<T extends (...args: any[]) => void>(fn: T, limit: number, opts?: {
leading?: boolean;
trailing?: boolean;
}): (...args: Parameters<T>) => void;
/**
* Retries an asynchronous function a given number of times with optional delay/backoff.
*
* @template T
* @param {() => Promise<T>} fn - The async function to retry.
* @param {number} [retries=3] - Number of retry attempts.
* @param {number} [delay=500] - Delay in milliseconds between retries.
* @param {boolean} [backoff=false] - Use exponential backoff between attempts.
* @param {(error: unknown, attempt: number) => void} [onRetry] - Callback for each retry attempt.
* @returns {Promise<T>} The result of the async function if successful.
* @throws {*} The last encountered error if all retries fail.
*/
declare function retry<T>(fn: () => Promise<T>, retries?: number, delay?: number, backoff?: boolean, onRetry?: (error: unknown, attempt: number) => void): Promise<T>;
/**
* Wraps a promise and rejects it if it doesn't resolve within the specified timeout.
*
* @template T
* @param {Promise<T>} promise - The original promise.
* @param {number} ms - Timeout in milliseconds.
* @param {string} [message="Operation timed out"] - Optional timeout message.
* @returns {Promise<T>} A promise that resolves or rejects within the timeout.
*/
declare function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>;
/**
* Executes async tasks in true batches.
* Each batch runs in parallel, but batches run sequentially.
* All tasks in a batch start at the same time, next batch waits for full completion.
* NOTE: For more granular concurrency, use a "queue" or "pooled" approach.
*
* @template T
* @param {Array<() => Promise<T>>} tasks - An array of functions that return Promises.
* @param {number} limit - Number of tasks to run in parallel per batch.
* @returns {Promise<T[]>} A promise that resolves to an array of resolved values.
*/
declare function runInBatches<T>(tasks: (() => Promise<T>)[], limit: number): Promise<T[]>;
/**
* Wraps a function and ensures it is only called once at a time.
* Calls made while one is in progress will wait for the same Promise.
* Optionally, new calls can be dropped while in progress (drop=true).
*
* @template TArgs
* @template TResult
* @param {(...args: TArgs) => Promise<TResult>} fn - The async function to wrap.
* @param {boolean} [drop=false] - If true, new calls while one is pending are rejected.
* @returns {(...args: TArgs) => Promise<TResult>} A wrapped function with singleton behavior.
*/
declare function singletonAsync<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>, drop?: boolean): (...args: TArgs) => Promise<TResult>;
/**
* Resolves a list of async tasks in parallel, returning both resolved and rejected results.
*
* @template T
* @param {Array<() => Promise<T>>} tasks - Array of promise-returning functions.
* @returns {Promise<PromiseSettledResult<T>[]>} Results including status and value/reason.
*/
declare function settleAll<T>(tasks: (() => Promise<T>)[]): Promise<PromiseSettledResult<T>[]>;
interface TaskQueue {
<T>(taskFn: () => Promise<T>): Promise<T>;
pause: () => void;
resume: () => void;
readonly length: number;
readonly isPaused: boolean;
}
/**
* A simple task queue that executes async tasks with a concurrency limit.
* Exposes pause, resume, and queue length getters.
*
* @param {number} limit - Maximum number of concurrent tasks.
* @returns {function & { pause: () => void, resume: () => void, length: number, isPaused: boolean }}
* Enqueue function plus queue controls.
*/
declare function createTaskQueue(limit: number): TaskQueue;
/**
* Executes async functions sequentially and collects results.
* Useful when order matters or tasks depend on each other.
*
* @template T
* @param {Array<() => Promise<T>>} tasks - Array of promise-returning functions.
* @returns {Promise<T[]>} Array of resolved values.
*/
declare function runInSeries<T>(tasks: (() => Promise<T>)[]): Promise<T[]>;
/**
* Memoizes an async function, caching results for repeated calls with identical arguments.
* Optional TTL (time-to-live) for cached entries.
*
* @template T Function return type
* @template Args Function arguments types
* @param {(...args: Args) => Promise<T>} fn - The async function to memoize
* @param {object} [options] - Memoization options
* @param {number} [options.ttl] - Cache TTL in milliseconds (optional)
* @param {(args: Args) => string} [options.keyFn] - Custom key generator function
* @returns {(...args: Args) => Promise<T>} Memoized function
*/
declare function memoizeAsync<T, Args extends any[]>(fn: (...args: Args) => Promise<T>, options?: {
ttl?: number;
keyFn?: (args: Args) => string;
}): (...args: Args) => Promise<T>;
/**
* Creates an abortable version of a promise that can be cancelled using an AbortController.
*
* @template T
* @param {Promise<T>} promise - The promise to make abortable
* @param {AbortSignal} signal - AbortSignal from AbortController
* @param {any} [abortValue] - Value to use when rejecting on abort
* @returns {Promise<T>} Promise that rejects if the signal is aborted
*/
declare function abortable<T>(promise: Promise<T>, signal: AbortSignal, abortValue?: any): Promise<T>;
/**
* Creates a promise with external resolve/reject functions.
* Useful for creating promises that can be resolved or rejected from outside.
*
* @template T
* @returns {[Promise<T>, (value: T | PromiseLike<T>) => void, (reason?: any) => void]}
* Tuple of [promise, resolve, reject]
*/
declare function createDeferred<T>(): [Promise<T>, (value: T | PromiseLike<T>) => void, (reason?: any) => void];
/**
* Chains a series of async functions, passing the result of each to the next.
* Similar to function composition but for async functions.
*
* @template T
* @param {Array<(input: any) => Promise<any>>} fns - Array of async functions to compose
* @returns {(input: any) => Promise<T>} Composed function
*/
declare function waterfall<T>(fns: Array<(input: any) => Promise<any>>): (initialValue: any) => Promise<T>;
/**
* Creates a rate limiter that ensures functions aren't called more than
* a specified number of times per interval.
*
* @template T
* @param {(...args: any[]) => Promise<T>} fn - Function to rate limit
* @param {number} maxCalls - Maximum calls allowed per interval
* @param {number} interval - Time interval in milliseconds
* @returns {(...args: any[]) => Promise<T>} Rate limited function
*/
declare function rateLimit<T>(fn: (...args: any[]) => Promise<T>, maxCalls: number, interval: number): (...args: any[]) => Promise<T>;
/**
* Circuit breaker pattern implementation for protecting against cascading failures.
* Tracks failures and prevents calling the function when too many failures occur.
*
* @param fn - Function to protect with circuit breaker
* @param options - Circuit breaker options
* @returns Function wrapped with circuit breaker logic
*
* @example
* ```typescript
* const protectedFetch = circuitBreaker(
* async (url) => {
* const response = await fetch(url);
* if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
* return response.json();
* },
* {
* failureThreshold: 3,
* resetTimeout: 30000,
* onOpen: () => console.log('Circuit breaker opened'),
* onClose: () => console.log('Circuit breaker closed')
* }
* );
*
* // Will throw CircuitBreakerOpenError after failureThreshold consecutive failures
* try {
* const data = await protectedFetch('https://api.example.com');
* } catch (error) {
* if (error instanceof CircuitBreakerOpenError) {
* console.log('Service is currently unavailable, please try again later');
* }
* }
* ```
*/
declare class CircuitBreakerOpenError extends Error {
constructor(message?: string);
}
/**
* Circuit breaker states
*/
declare enum CircuitBreakerState {
CLOSED = "CLOSED",
OPEN = "OPEN",
HALF_OPEN = "HALF_OPEN"
}
interface CircuitBreakerOptions {
/** Number of consecutive failures before opening circuit (default: 5) */
failureThreshold?: number;
/** Time in milliseconds to wait before trying again (default: 10000) */
resetTimeout?: number;
/** Number of successful calls to close the circuit again (default: 1) */
successThreshold?: number;
/** Callback when circuit opens */
onOpen?: () => void;
/** Callback when circuit closes */
onClose?: () => void;
/** Callback when circuit enters half-open state */
onHalfOpen?: () => void;
}
declare function circuitBreaker<T, Args extends any[]>(fn: (...args: Args) => Promise<T>, options?: CircuitBreakerOptions): (...args: Args) => Promise<T>;
/**
* Run multiple async tasks with concurrency control.
*
* @param tasks - Array of async tasks to run
* @param options - Concurrency options
* @returns Promise that resolves when all tasks are complete
*
* @example
* ```typescript
* const urls = ['https://example.com/1', 'https://example.com/2', many more ];
*
* // Process up to 5 requests at a time, with progress reporting
* const results = await runWithConcurrency(
* urls.map(url => () => fetch(url).then(res => res.json())),
* {
* concurrency: 5,
* onProgress: (completed, total) => {
* console.log(`Progress: ${completed}/${total}`);
* }
* }
* );
* ```
*/
declare function runWithConcurrency<T>(tasks: Array<() => Promise<T>>, options?: {
/** Maximum number of tasks to run at once (default: 3) */
concurrency?: number;
/** Called whenever a task completes */
onProgress?: (completed: number, total: number) => void;
/** Abort signal to cancel execution */
signal?: AbortSignal;
}): Promise<T[]>;
/**
* Optionally requires a module, returning null if not found.
*
* @template T
* @param {string} name - The name of the module to require.
* @returns {T | null} The required module or null if not found.
*/
declare function optionalRequire<T = any>(name: string): T | null;
/**
* Races promises and returns the first fulfilled value with its index.
* If all promises reject, the last rejection is thrown.
*
* @template T
* @param {Promise<T>[]} promises - Array of promises.
* @returns {Promise<{ value: T; index: number }>} First fulfilled value with index.
*
* @example
* const result = await raceWithValue([fetchA(), fetchB(), fetchC()]);
* console.log(result.value, result.index); // First fulfilled promise result
*/
declare function raceWithValue<T>(promises: Promise<T>[]): Promise<{
value: T;
index: number;
}>;
export { CircuitBreakerOpenError, CircuitBreakerState, abortable, circuitBreaker, createDeferred, createTaskQueue, debounce, memoizeAsync, optionalRequire, raceWithValue, rateLimit, retry, runInBatches, runInSeries, runWithConcurrency, settleAll, singletonAsync, sleep, throttle, waterfall, withTimeout };
export type { CircuitBreakerOptions, TaskQueue };