Package Exports
- @mxweb/utils
- @mxweb/utils/chunk
- @mxweb/utils/flatten
- @mxweb/utils/format-size
- @mxweb/utils/get-env
- @mxweb/utils/http
- @mxweb/utils/interpolate
- @mxweb/utils/package.json
- @mxweb/utils/pascal-to-kebab
- @mxweb/utils/rate-limiter
- @mxweb/utils/retry
- @mxweb/utils/sleep
- @mxweb/utils/storage
- @mxweb/utils/types
- @mxweb/utils/uri
Readme
@mxweb/utils
A comprehensive collection of TypeScript utilities for modern web development, providing robust solutions for HTTP requests, data manipulation, storage management, and more.
Table of Contents
- Installation
- Features
- Usage
- API Reference
- Examples
- Configuration
- TypeScript Support
- Browser Support
- Contributing
- License
- Changelog
- Authors
- Acknowledgments
Installation
Install @mxweb/utils using your preferred package manager:
# Using npm
npm install @mxweb/utils
# Using yarn
yarn add @mxweb/utils
# Using pnpm
pnpm add @mxweb/utils
# Using bun
bun add @mxweb/utilsFeatures
- ๐ HTTP Client - Full-featured HTTP client with interceptors, authentication, and file upload support
- ๐พ Storage Management - Unified API for localStorage, sessionStorage, and cookies
- ๐ง String Utilities - Case conversion, URI encoding/decoding, template interpolation
- ๐ฆ Object Utilities - Deep flattening, array conversion, and regex key escaping
- ๐ Data Formatting - Human-readable file size formatting
- ๐ Environment Variables - Cross-framework environment variable access (React, Next.js, Vite, etc.)
- โฑ๏ธ Async Utilities - Promise-based delay, sleep functions, automatic retry logic, and rate limiting
- ๐ Array Utilities - Array chunking and batch processing
- ๐ Type-Safe - Full TypeScript support with comprehensive type definitions
- ๐ Framework Agnostic - Works with any JavaScript framework or vanilla JS
- ๐ฏ Tree-Shakeable - Import only what you need for optimal bundle size
- ๐ฑ Browser & Node.js - Compatible with both environments
Usage
Importing
You can import utilities in multiple ways:
// Import everything
import * as utils from "@mxweb/utils";
// Import specific utilities
import { Http, storage, chunk, formatSize } from "@mxweb/utils";
// Import from specific modules
import { Http } from "@mxweb/utils/http";
import { storage } from "@mxweb/utils/storage";
import { chunk } from "@mxweb/utils/chunk";Quick Start
Here are some quick examples to get you started:
import { Http, storage, chunk, formatSize, sleep, getEnv, Retry } from "@mxweb/utils";
// 1. HTTP Client - Make API requests
const http = new Http("https://api.example.com");
// GET request
const { data, success } = await http.get("/users");
// POST request
await http.post("/users", { name: "John Doe", email: "john@example.com" });
// Upload file with progress
await http.upload("/upload", file, {
name: "document",
onProgress: (progress) => console.log(`${progress.percentage}%`),
});
// 2. Storage - Manage localStorage, sessionStorage, cookies
// Save data
storage.local.setItem("user", { id: 1, name: "John" });
// Retrieve data with type safety
const user = storage.local.getItem<{ id: number; name: string }>("user");
// 3. Array Utilities - Split arrays into chunks
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const chunks = chunk(numbers, 3); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
// 4. Format Utilities - Human-readable file sizes
const size = formatSize(1536000); // "1.46 MB"
// 5. Async Utilities - Add delays and retry logic
await sleep(1000); // Wait 1 second
// Retry operations with automatic error handling
const retry = new Retry({ maxRetries: 3, delay: 1000 });
const result = await retry.execute(async () => {
const response = await fetch("/api/unreliable-endpoint");
if (!response.ok) throw new Error("Request failed");
return response.json();
});
// Rate limit API calls
const limiter = new RateLimiter({ maxRequests: 5, interval: 1000 });
await limiter.handle(() => fetch("/api/data"));
// 6. Environment Variables - Access env vars across frameworks
const apiUrl = getEnv("API_URL", "http://localhost:3000");
// Automatically checks: API_URL, REACT_APP_API_URL, NEXT_PUBLIC_API_URL, VITE_API_URL, etc.API Reference
Type Definitions
Common TypeScript type definitions used throughout the library.
ObjectOf<T>
Generic object type with string keys and values of type T. Supports both Record and index signature syntax.
type ObjectOf<T = unknown> =
| Record<string, T>
| {
[key: string]: T;
};Example:
import { ObjectOf } from "@mxweb/utils";
const numbers: ObjectOf<number> = { a: 1, b: 2, c: 3 };
const users: ObjectOf<User> = {
user1: { id: 1, name: "John" },
user2: { id: 2, name: "Jane" },
};Callback<T, Args>
A synchronous callback function type.
interface Callback<T, Args extends unknown[] = []> {
(...args: Args): T;
}Example:
import { Callback } from "@mxweb/utils";
const add: Callback<number, [number, number]> = (a, b) => a + b;
const greet: Callback<string, [string]> = (name) => `Hello, ${name}!`;AsyncCallback<T, Args>
An asynchronous callback function type that returns a Promise.
interface AsyncCallback<T, Args extends unknown[] = []> {
(...args: Args): Promise<T>;
}Example:
import { AsyncCallback } from "@mxweb/utils";
const fetchUser: AsyncCallback<User, [string]> = async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};Primitive
Primitive JavaScript types.
type Primitive = string | number | boolean | null | undefined;Example:
import { Primitive } from "@mxweb/utils";
const values: Primitive[] = ["text", 42, true, null, undefined];FlattenedPrimitive
Object with string keys and primitive values. Commonly used for flattened object representations.
type FlattenedPrimitive = Record<string, Primitive>;Example:
import { FlattenedPrimitive } from "@mxweb/utils";
const flatData: FlattenedPrimitive = {
"user.name": "John",
"user.age": 30,
"user.active": true,
"user.email": null,
};Array Utilities
chunk
Splits an array into smaller chunks of a specified size.
function chunk<T>(source: T[], size?: number): T[][];Parameters:
source: T[]- The array to split into chunkssize: number- The maximum size of each chunk (default: 5)
Returns: T[][] - A two-dimensional array of chunks
Example:
import { chunk } from "@mxweb/utils";
chunk([1, 2, 3, 4, 5, 6, 7], 3);
// Returns: [[1, 2, 3], [4, 5, 6], [7]]
// Batch processing
const users = Array.from({ length: 100 }, (_, i) => ({ id: i }));
const batches = chunk(users, 10); // Process 10 users at a timeString Utilities
pascalToKebab
Converts a PascalCase or camelCase string to kebab-case.
function pascalToKebab(str: string): string;Parameters:
str: string- The PascalCase or camelCase string to convert
Returns: string - The kebab-case formatted string
Example:
import { pascalToKebab } from "@mxweb/utils";
pascalToKebab("PascalCase"); // "pascal-case"
pascalToKebab("camelCase"); // "camel-case"
pascalToKebab("XMLHttpRequest"); // "xml-http-request"interpolate
Interpolates a template string by replacing placeholders with values.
function interpolate(pattern: string, params?: Record<string, unknown> | Array<unknown>): string;Parameters:
pattern: string- Template string with placeholders in format{key}params: Record<string, unknown> | Array<unknown>- Values to interpolate
Returns: string - The interpolated string
Example:
import { interpolate } from "@mxweb/utils";
interpolate("Hello {name}!", { name: "John" });
// Returns: "Hello John!"
interpolate("User: {user.name}, Age: {user.age}", {
user: { name: "John", age: 30 },
});
// Returns: "User: John, Age: 30"
interpolate("/api/users/{userId}/posts/{postId}", {
userId: 123,
postId: 456,
});
// Returns: "/api/users/123/posts/456"decodeURISafe
Safely decodes a URI-encoded string with error handling.
function decodeURISafe(str: string, component?: boolean): string;Parameters:
str: string- The URI-encoded string to decodecomponent: boolean- UsedecodeURIComponent(true) ordecodeURI(false). Default: true
Returns: string - The decoded string, or original if decoding fails
Example:
import { decodeURISafe } from "@mxweb/utils";
decodeURISafe("Hello%20World"); // "Hello World"
decodeURISafe("Hello%2World"); // "Hello%2World" (invalid encoding preserved)encodeURISafe
Encodes a string to be safely used in a URI.
function encodeURISafe(str: string, component?: boolean): string;Parameters:
str: string- The string to encodecomponent: boolean- UseencodeURIComponent(true) orencodeURI(false). Default: true
Returns: string - The URI-encoded string
Example:
import { encodeURISafe } from "@mxweb/utils";
encodeURISafe("Hello World"); // "Hello%20World"
encodeURISafe("name=value&other=data"); // "name%3Dvalue%26other%3Ddata"Object Utilities
flatten
Flattens a nested object or array into a single-level object with dot-notated keys.
function flatten(data: Record<string, unknown> | Array<unknown>): Record<string, unknown>;Parameters:
data: Record<string, unknown> | Array<unknown>- The object or array to flatten
Returns: Record<string, unknown> - A flattened object with dot-notated keys
Example:
import { flatten } from "@mxweb/utils";
flatten({
user: {
name: "John",
contacts: { email: "john@example.com" },
},
});
// Returns: { "user.name": "John", "user.contacts.email": "john@example.com" }
flatten([1, 2, { value: 3 }]);
// Returns: { "0": 1, "1": 2, "2.value": 3 }flattenArray
Flattens an array into a single-level object with dot-notated keys.
function flattenArray(data: Array<unknown>): Record<string, unknown>;Parameters:
data: Array<unknown>- The array to flatten
Returns: Record<string, unknown> - A flattened object
Example:
import { flattenArray } from "@mxweb/utils";
flattenArray([{ name: "John" }, { name: "Jane" }]);
// Returns: { "0.name": "John", "1.name": "Jane" }flattenObject
Flattens an object into a single-level object with dot-notated keys.
function flattenObject(data: Record<string, unknown>): Record<string, unknown>;Parameters:
data: Record<string, unknown>- The object to flatten
Returns: Record<string, unknown> - A flattened object
Example:
import { flattenObject } from "@mxweb/utils";
flattenObject({ user: { name: "John", age: 30 } });
// Returns: { "user.name": "John", "user.age": 30 }flattenToArray
Converts flattened data back into array structure based on a specific path.
function flattenToArray(data: FlattenedPrimitive, path: string): Array<unknown>;Parameters:
data: FlattenedPrimitive- The flattened data object containing dot-notation keyspath: string- The path prefix to extract and convert to array structure
Returns: Array<unknown> - An array containing either reconstructed objects (for array data) or key-value pairs (for object data)
Example:
import { flattenToArray } from "@mxweb/utils";
// Reconstruct array from flattened data
const flatData = {
"users.[0].name": "John",
"users.[0].age": 30,
"users.[1].name": "Jane",
"users.[1].age": 25,
"config.theme": "dark",
"config.language": "en",
};
// Extract array structure
flattenToArray(flatData, "users");
// Returns: [
// { "name": "John", "age": 30 },
// { "name": "Jane", "age": 25 }
// ]
// Extract object structure as key-value pairs
flattenToArray(flatData, "config");
// Returns: [
// { key: "theme", value: "dark" },
// { key: "language", value: "en" }
// ]
// No matching path
flattenToArray(flatData, "nonexistent");
// Returns: []escapeRegexKey
Escapes special regex characters in strings for safe use in RegExp constructor.
function escapeRegexKey(key: string): string;Parameters:
key: string- The string containing characters to escape for regex usage
Returns: string - The string with special regex characters properly escaped
Escaped Characters:
.(dot) โ\.[and](square brackets) โ\[and\]{and}(curly braces) โ\{and\}
Example:
import { escapeRegexKey } from "@mxweb/utils";
// Basic escaping
escapeRegexKey("user.name"); // "user\.name"
escapeRegexKey("data[0]"); // "data\[0\]"
escapeRegexKey("config{env}"); // "config\{env\}"
// Multiple special characters
escapeRegexKey("path.to[item].{key}"); // "path\.to\[item\]\.{key}"
// Use with RegExp constructor
const keyToFind = "user.settings[theme]";
const escapedKey = escapeRegexKey(keyToFind);
const regex = new RegExp(`^${escapedKey}$`);
regex.test("user.settings[theme]"); // true
regex.test("user_settings_theme"); // false
// Pattern matching in flattened data
const flatData = {
"api.users[0].profile.name": "John",
"api.users[1].profile.name": "Jane",
"config.database.host": "localhost",
};
const pattern = escapeRegexKey("api.users[0]");
const regex = new RegExp(`^${pattern}`);
Object.keys(flatData).filter((key) => regex.test(key));
// Returns: ["api.users[0].profile.name"]
// Template variable escaping
escapeRegexKey("config.{environment}.database.{host}");
// Returns: "config\.{environment}\.database\.{host}"File & Format Utilities
formatSize
Formats a file size in bytes to a human-readable string.
function formatSize(sizeInBytes: number, base?: Array<string>): string;Parameters:
sizeInBytes: number- The size in bytes to formatbase: Array<string>- Custom unit labels (default:["B", "KB", "MB", "GB", "TB"])
Returns: string - Formatted string with size and unit
Example:
import { formatSize } from "@mxweb/utils";
formatSize(1024); // "1 KB"
formatSize(1536); // "1.5 KB"
formatSize(1048576); // "1 MB"
formatSize(2457600); // "2.34 MB"HTTP Client
Http
Full-featured HTTP client with interceptors, authentication, and file upload support.
Constructor
new Http(baseURL?: string)Parameters:
baseURL: string- Base URL for all requests (default: fromAPI_URLenv var or "/")
Example:
import { Http } from "@mxweb/utils";
const http = new Http("https://api.example.com");Instance Methods
get
Makes a GET request.
get<T = unknown, Query = Record<string, unknown>, E = unknown>(
url: string,
query?: Query,
options?: { headers?, params?, signal? }
): Promise<HttpResponse<T, E>>Example:
const { data, success, status } = await http.get<User[]>("/users");
const response = await http.get("/search", { q: "keyword", page: 1 });post
Makes a POST request.
post<T = unknown, Body = unknown, E = unknown>(
url: string,
body?: Body,
options?: { headers?, params?, signal?, query? }
): Promise<HttpResponse<T, E>>Example:
const response = await http.post<User>("/users", {
name: "John Doe",
email: "john@example.com",
});put
Makes a PUT request.
put<T = unknown, Body = unknown, E = unknown>(
url: string,
body?: Body,
options?: { headers?, params?, signal?, query? }
): Promise<HttpResponse<T, E>>Example:
await http.put(
"/users/{id}",
{ name: "John Updated" },
{
params: { id: 123 },
}
);patch
Makes a PATCH request.
patch<T = unknown, Body = unknown, E = unknown>(
url: string,
body?: Body,
options?: { headers?, params?, signal?, query? }
): Promise<HttpResponse<T, E>>Example:
await http.patch(
"/users/{id}",
{ email: "newemail@example.com" },
{
params: { id: 123 },
}
);delete
Makes a DELETE request.
delete<T = unknown, E = unknown>(
url: string,
options?: { headers?, params?, signal?, query? }
): Promise<HttpResponse<T, E>>Example:
await http.delete("/users/{id}", { params: { id: 123 } });head
Makes a HEAD request.
head<T = unknown, E = unknown>(
url: string,
options?: { headers?, params?, signal?, query? }
): Promise<HttpResponse<T, E>>options
Makes an OPTIONS request.
options<T = unknown, E = unknown>(
url: string,
options?: { headers?, params?, signal?, query? }
): Promise<HttpResponse<T, E>>upload
Uploads files with optional progress tracking.
upload<T = unknown, E = unknown>(
url: string,
file: File | File[] | FileList,
options?: HttpUploadOptions
): Promise<HttpResponse<T, E>>Example:
const file = input.files[0];
await http.upload("/upload", file, {
name: "document",
onProgress: (progress) => {
console.log(`${progress.percentage}%`);
},
body: { userId: 123 },
});
// Multiple files
await http.upload("/upload", input.files, {
name: "files[]",
});on / off
Registers or removes interceptors for this instance.
on(type: 'request' | 'response' | 'error', handler: Function): Http
off(type: 'request' | 'response' | 'error', handler: Function): HttpExample:
http.on("request", async (options) => {
console.log("Request:", options.url);
return options;
});
http.on("response", async (response) => {
console.log("Response:", response.status);
return response;
});
http.on("error", async (error) => {
console.error("Error:", error);
});addHeaders
Adds default headers to all requests.
addHeaders(headers: Record<string, string>): voidExample:
http.addHeaders({
"X-Custom-Header": "value",
"X-API-Version": "2.0",
});setStorage
Sets the storage implementation for authentication tokens.
setStorage(storage: HttpStorage): voidExample:
http.setStorage(storage.local);Static Methods
Http.on / Http.off
Registers or removes global interceptors for all Http instances.
static on(type: 'request' | 'response' | 'error', handler: Function): void
static off(type: 'request' | 'response' | 'error', handler: Function): voidExample:
// Global request interceptor
Http.on("request", async (options) => {
options.headers = { ...options.headers, "X-Timestamp": Date.now() };
return options;
});Http.createInfer
Creates a factory function for generating type-safe API endpoint functions.
static createInfer(options?: HttpInferOptions): InferFunctionExample:
const endpoints = {
"user.list": "/api/users",
"user.get": "/api/users/{id}",
"user.create": "/api/users",
};
const infer = Http.createInfer({
baseURL: "https://api.example.com",
endpoint: endpoints,
});
const getUsers = infer<User[]>("user.list", HttpMethod.GET);
const createUser = infer<User, [UserInput]>("user.create", HttpMethod.POST);
// Use the generated functions
const { data: users } = await getUsers.fn();
const { data: newUser } = await createUser.fn({ name: "John" });Types & Interfaces
HttpMethod
Enum of supported HTTP methods.
enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
PATCH = "PATCH",
HEAD = "HEAD",
OPTIONS = "OPTIONS",
}HttpRequest
Configuration options for an HTTP request.
interface HttpRequest<Body, Params, Query, Headers> {
method: HttpMethod;
url: string;
headers?: Headers;
body?: Body;
query?: Query;
params?: Params;
signal?: AbortSignal;
}HttpResponse
Standardized HTTP response structure.
interface HttpResponse<T, E> {
success: boolean;
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
error: E | null;
}HttpStorage
Interface for storage implementations.
interface HttpStorage {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string, options?: any): void;
}HttpUploadOptions
Configuration options for file uploads.
interface HttpUploadOptions {
name: string;
onProgress?: (progress: HttpProgress) => void;
body?: Record<string, unknown>;
headers?: Record<string, string>;
params?: Record<string, unknown>;
signal?: AbortSignal;
query?: HttpQuery;
}HttpProgress
Progress information for file uploads.
interface HttpProgress {
loaded: number;
total: number;
percentage: number;
}Storage Utilities
storage
Unified storage utility for localStorage, sessionStorage, and cookies.
storage.local
Access localStorage with automatic JSON serialization.
storage.local.getItem<T>(key: string): T | null
storage.local.setItem(key: string, value: any): void
storage.local.removeItem(key: string): voidExample:
import { storage } from "@mxweb/utils";
// Store data
storage.local.setItem("user", { id: 1, name: "John" });
// Retrieve with type safety
const user = storage.local.getItem<{ id: number; name: string }>("user");
// Remove
storage.local.removeItem("user");storage.session
Access sessionStorage with automatic JSON serialization.
storage.session.getItem<T>(key: string): T | null
storage.session.setItem(key: string, value: any): void
storage.session.removeItem(key: string): voidExample:
storage.session.setItem("tempData", { timestamp: Date.now() });
const data = storage.session.getItem("tempData");storage.cookie
Access cookies with automatic JSON serialization.
storage.cookie.getItem<T>(key: string): T | null
storage.cookie.setItem(key: string, value: any): void
storage.cookie.removeItem(key: string): voidExample:
storage.cookie.setItem("preferences", { theme: "dark", lang: "en" });
const prefs = storage.cookie.getItem("preferences");storage.from
Create a storage instance of a specific type.
storage.from(type: 'localStorage' | 'sessionStorage' | 'cookie'): InnerStorageExample:
const customStorage = storage.from("localStorage");
customStorage.setItem("key", "value");Environment Variables
getEnv
Gets environment variables with automatic framework prefix detection.
function getEnv<T>(key: string, defaultValue?: T): T | undefined;Parameters:
key: string- The environment variable key (case-insensitive)defaultValue: T- Default value if variable not found
Returns: The environment variable value or default value
Supported Prefixes:
API_URLREACT_API_URL/REACT_APP_API_URLNEXT_PUBLIC_API_URLVITE_API_URLPUBLIC_API_URLNUXT_PUBLIC_API_URLGATSBY_API_URLSNOWPACK_PUBLIC_API_URLASTRO_PUBLIC_API_URL
Example:
import { getEnv } from "@mxweb/utils";
const apiUrl = getEnv("API_URL", "http://localhost:3000");
const apiKey = getEnv("API_KEY");
const debug = getEnv("DEBUG", "false") === "true";Async Utilities
sleep
Pauses execution for a specified duration.
function sleep(ms: number): Promise<void>;Parameters:
ms: number- Duration in milliseconds (negative values clamped to 0)
Returns: Promise<void> - Promise that resolves after the delay
Example:
import { sleep } from "@mxweb/utils";
// Wait 1 second
await sleep(1000);
// Retry with delay
async function retryRequest(fn: () => Promise<any>, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (error) {
if (i < attempts - 1) await sleep(1000);
}
}
}Retry
A utility class for executing functions with automatic retry logic and configurable delays.
class Retry {
constructor(options?: RetryOptions);
setOptions(options: RetryOptions): Retry;
execute<T>(callback: Callback<T> | AsyncCallback<T>): Promise<T>;
static DEFAULT_DELAY: number;
}
interface RetryOptions {
maxRetries?: number; // Maximum number of retry attempts (default: 1)
delay?: number; // Delay between retries in milliseconds (default: 1500)
}Parameters:
options: RetryOptions- Configuration options for retry behavior
Methods:
setOptions(options: RetryOptions): Retry
Updates the retry configuration options and returns the instance for method chaining.
execute<T>(callback: Callback<T> | AsyncCallback<T>): Promise<T>
Executes a function with automatic retry logic on failure.
callback- The function to execute (can be sync or async)- Returns: Promise that resolves to the callback's return value
- Throws: Error when maximum retries are reached
Examples:
import { Retry } from "@mxweb/utils";
// Basic retry with default options (1 retry, 1500ms delay)
const retry = new Retry();
const result = await retry.execute(() => fetchData());
// Custom retry configuration
const retry = new Retry({ maxRetries: 3, delay: 1000 });
// Network request with retry
const data = await retry.execute(async () => {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
});
// Database operation with retry
const user = await retry.execute(async () => {
return await database.users.findById(userId);
});
// File operation with retry
const fileContent = await retry.execute(() => {
return fs.readFileSync(filePath, "utf8");
});
// Method chaining
const result = await new Retry()
.setOptions({ maxRetries: 5, delay: 2000 })
.execute(() => unreliableOperation());
// Error handling
try {
await retry.execute(() => {
throw new Error("This will fail");
});
} catch (error) {
console.error("All retries failed:", error.message);
// Output: "| Mailer Retry > Max retries reached (3): This will fail"
}
// Real-world example: API with exponential backoff
class ApiClient {
private retry = new Retry({ maxRetries: 3, delay: 1000 });
async get(url: string) {
return this.retry.execute(async () => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
});
}
async post(url: string, data: any) {
return this.retry.execute(async () => {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
});
}
}
// Usage with different retry strategies
const quickRetry = new Retry({ maxRetries: 2, delay: 500 });
const patientRetry = new Retry({ maxRetries: 5, delay: 3000 });
// Quick operations
const cache = await quickRetry.execute(() => redis.get(key));
// Critical operations that need more patience
const payment = await patientRetry.execute(() => processPayment(order));RateLimiter
A rate limiter that controls the frequency of function execution. Ensures that callbacks are executed within specified rate limits by queueing requests and enforcing delays between executions.
class RateLimiter {
constructor(options: RateLimitOptions);
setOptions(options: RateLimitOptions): RateLimiter;
handle<T>(callback: Callback<T>): Promise<T>;
static DEFAULT_DELAY: number; // 1500ms
static DEFAULT_MAX_REQUESTS: number; // 1
}
interface RateLimitOptions {
maxRequests?: number; // Maximum requests allowed per interval (default: 1)
interval?: number; // Time interval in milliseconds (default: 1500)
}Parameters:
options: RateLimitOptions- Configuration options for rate limiting behavior
Methods:
setOptions(options: RateLimitOptions): RateLimiter
Updates the rate limiter configuration options and returns the instance for method chaining.
handle<T>(callback: Callback<T>): Promise<T>
Executes a function with rate limiting. The callback is queued and executed when rate limits allow.
callback- The function to execute (can be sync or async)- Returns: Promise that resolves to the callback's return value
- Throws: Any error thrown by the callback
Examples:
import { RateLimiter } from "@mxweb/utils";
// Basic rate limiter - 5 requests per second
const limiter = new RateLimiter({
maxRequests: 5,
interval: 1000,
});
// Execute rate-limited requests
for (let i = 0; i < 10; i++) {
limiter.handle(async () => {
console.log(`Request ${i + 1}`);
return fetch(`/api/data/${i}`);
});
}
// API rate limiting
const apiLimiter = new RateLimiter({ maxRequests: 3, interval: 2000 });
const results = await Promise.all([
apiLimiter.handle(() => fetch("/api/users")),
apiLimiter.handle(() => fetch("/api/posts")),
apiLimiter.handle(() => fetch("/api/comments")),
apiLimiter.handle(() => fetch("/api/likes")), // Will wait for rate limit
]);
// Database query rate limiting
const dbLimiter = new RateLimiter({ maxRequests: 10, interval: 1000 });
const users = await Promise.all(
userIds.map((id) => dbLimiter.handle(() => database.users.findById(id)))
);
// Method chaining
const result = await new RateLimiter({ maxRequests: 2, interval: 1000 })
.setOptions({ maxRequests: 5, interval: 500 })
.handle(() => fetch("/api/data"));
// File download rate limiting
const downloadLimiter = new RateLimiter({ maxRequests: 2, interval: 1500 });
async function downloadFile(url: string) {
return downloadLimiter.handle(async () => {
const response = await fetch(url);
return response.blob();
});
}
// Real-world example: Third-party API client with rate limits
class ThirdPartyApiClient {
private limiter = new RateLimiter({ maxRequests: 10, interval: 1000 });
async get(endpoint: string) {
return this.limiter.handle(async () => {
const response = await fetch(`https://api.example.com${endpoint}`);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
});
}
async post(endpoint: string, data: any) {
return this.limiter.handle(async () => {
const response = await fetch(`https://api.example.com${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
});
}
// Update rate limits dynamically
setRateLimit(maxRequests: number, interval: number) {
this.limiter.setOptions({ maxRequests, interval });
}
}
// Batch processing with rate limiting
const batchLimiter = new RateLimiter({ maxRequests: 5, interval: 2000 });
async function processBatch(items: any[]) {
const results = await Promise.all(
items.map((item) =>
batchLimiter.handle(async () => {
// Process each item
return await processItem(item);
})
)
);
return results;
}
// Combining with Retry for robust API calls
const limiter = new RateLimiter({ maxRequests: 5, interval: 1000 });
const retry = new Retry({ maxRetries: 3, delay: 1000 });
async function robustApiCall(url: string) {
return limiter.handle(() =>
retry.execute(async () => {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
);
}
// Different rate limits for different priority levels
const highPriorityLimiter = new RateLimiter({ maxRequests: 10, interval: 1000 });
const lowPriorityLimiter = new RateLimiter({ maxRequests: 2, interval: 1000 });
async function fetchWithPriority(url: string, priority: "high" | "low") {
const limiter = priority === "high" ? highPriorityLimiter : lowPriorityLimiter;
return limiter.handle(() => fetch(url));
}Examples
Basic Examples
import {
Http,
storage,
chunk,
formatSize,
sleep,
pascalToKebab,
interpolate,
flatten,
getEnv,
Retry,
RateLimiter,
} from "@mxweb/utils";
// HTTP Client
const http = new Http("https://api.example.com");
const { data } = await http.get("/users");
await http.post("/users", { name: "John" });
// Storage
storage.local.setItem("user", { id: 1, name: "John" });
const user = storage.local.getItem("user");
// String utilities
pascalToKebab("MyComponent"); // 'my-component'
interpolate("Hello {{name}}", { name: "John" }); // 'Hello John'
// Array utilities
chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
flatten([
[1, 2],
[3, [4, 5]],
]); // [1, 2, 3, 4, 5]
// Format utilities
formatSize(1024); // '1 KB'
formatSize(1048576); // '1 MB'
// Async utilities
await sleep(1000); // Wait 1 second
// Retry utilities
const retry = new Retry({ maxRetries: 3, delay: 1000 });
await retry.execute(() => unreliableOperation());
// Rate limiting
const limiter = new RateLimiter({ maxRequests: 5, interval: 1000 });
await limiter.handle(() => apiCall());
// Environment variables
const apiUrl = getEnv("API_URL", "https://api.example.com");HTTP Client Examples
Type-Safe API with createInfer
import { Http, HttpMethod } from "@mxweb/utils";
const endpoints = {
"user.list": "/users",
"user.get": "/users/{id}",
"user.create": "/users",
} as const;
const infer = Http.createInfer({
baseURL: "https://api.example.com",
endpoint: endpoints,
});
interface User {
id: number;
name: string;
email: string;
}
const api = {
user: {
list: infer<User[]>("user.list", HttpMethod.GET),
get: infer<User, [{ params: { id: number } }]>("user.get", HttpMethod.GET),
create: infer<User, [{ name: string; email: string }]>("user.create", HttpMethod.POST),
},
};
// Usage
const users = await api.user.list.fn();
const user = await api.user.get.fn({ params: { id: 1 } });
const newUser = await api.user.create.fn({ name: "John", email: "john@example.com" });Server-Side Actions (Next.js)
// lib/api/server-client.ts
'use server';
import { Http, HttpMethod } from '@mxweb/utils';
import { getEnv } from '@mxweb/utils';
const serverHttp = new Http(getEnv('INTERNAL_API_URL'));
serverHttp.addHeaders({
'X-API-Key': getEnv('API_SECRET_KEY', ''),
});
const endpoints = {
'user.list': '/users',
'user.get': '/users/{id}',
};
const infer = Http.createInfer({
http: serverHttp,
endpoint: endpoints
});
export const serverApi = {
user: {
list: infer<User[]>('user.list', HttpMethod.GET),
get: infer<User, [{ params: { id: number } }]>('user.get', HttpMethod.GET),
},
};
// app/actions/user.actions.ts
'use server';
import { serverApi } from '@/lib/api/server-client';
export async function getUsersAction() {
const response = await serverApi.user.list.fn();
return response.data;
}
// app/users/page.tsx
'use client';
import { getUsersAction } from '@/app/actions/user.actions';
export default function UsersPage() {
const [users, setUsers] = useState([]);
useEffect(() => {
getUsersAction().then(setUsers);
}, []);
return <div>{/* Render users */}</div>;
}Storage Examples
import { storage } from "@mxweb/utils";
// User preferences
interface UserPreferences {
theme: "light" | "dark";
language: string;
}
storage.local.setItem("prefs", { theme: "dark", language: "en" });
const prefs = storage.local.getItem<UserPreferences>("prefs");
// Session management
interface Session {
userId: number;
expiresAt: number;
}
storage.session.setItem("session", {
userId: 123,
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
});
// Cookie-based auth
storage.cookie.setItem("access_token", "token123");
const token = storage.cookie.getItem<string>("access_token");Advanced Usage
API Client with Interceptors
import { Http, storage, sleep } from "@mxweb/utils";
class ApiClient {
private http: Http;
constructor(baseURL: string) {
this.http = new Http(baseURL);
this.http.setStorage(storage.local);
this.setupInterceptors();
}
private setupInterceptors() {
// Add auth token
this.http.on("request", async (options) => {
const token = storage.local.getItem<string>("access_token");
if (token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${token}`,
};
}
return options;
});
// Handle token refresh
this.http.on("response", async (response) => {
if (response.status === 401) {
const newToken = await this.refreshToken();
if (newToken) {
response.config.headers["Authorization"] = `Bearer ${newToken}`;
return await this.http.request(response.config);
}
}
return response;
});
// Retry on error
this.http.on("error", async (error) => {
if (!error.retryCount) error.retryCount = 0;
if (error.retryCount < 3) {
error.retryCount++;
await sleep(1000 * error.retryCount);
return await this.http.request(error.config);
}
return error;
});
}
private async refreshToken(): Promise<string | null> {
const refreshToken = storage.local.getItem<string>("refresh_token");
if (!refreshToken) return null;
const response = await this.http.post<{ accessToken: string }>("/auth/refresh", {
refreshToken,
});
if (response.success) {
storage.local.setItem("access_token", response.data.accessToken);
return response.data.accessToken;
}
return null;
}
async get<T>(url: string, query?: any) {
return this.http.get<T>(url, query);
}
async post<T>(url: string, body?: any) {
return this.http.post<T>(url, body);
}
}
// Usage
const api = new ApiClient("https://api.example.com");
const users = await api.get("/users");Configuration
Environment Variables
import { getEnv } from "@mxweb/utils";
// Works with any framework - automatically detects prefix
// React: REACT_APP_API_URL
// Next.js: NEXT_PUBLIC_API_URL
// Vite: VITE_API_URL
// Nuxt: NUXT_PUBLIC_API_URL
// Node.js: API_URL
const config = {
apiUrl: getEnv("API_URL", "https://api.example.com"),
apiKey: getEnv("API_KEY", ""),
debug: getEnv("DEBUG", "false") === "true",
};Authentication
import { Http, storage } from "@mxweb/utils";
const http = new Http("https://api.example.com");
http.setStorage(storage.local);
// Request interceptor - add auth token
http.on("request", async (options) => {
const token = storage.local.getItem<string>("access_token");
if (token) {
options.headers = { ...options.headers, Authorization: `Bearer ${token}` };
}
return options;
});
// Response interceptor - handle 401
http.on("response", async (response) => {
if (response.status === 401) {
// Refresh token or redirect to login
}
return response;
});Interceptors
import { Http, sleep } from "@mxweb/utils";
const http = new Http("https://api.example.com");
// Request interceptor
http.on("request", async (options) => {
options.headers = {
...options.headers,
"X-Request-ID": `req_${Date.now()}`,
};
return options;
});
// Response interceptor
http.on("response", async (response) => {
console.log(`Response: ${response.status}`);
return response;
});
// Error interceptor with retry
http.on("error", async (error) => {
if (!error.retryCount) error.retryCount = 0;
if (error.retryCount < 3) {
error.retryCount++;
await sleep(1000 * error.retryCount);
return await http.request(error.config);
}
return error;
});TypeScript Support
@mxweb/utils is written in TypeScript and provides full type safety with comprehensive type definitions for all utilities.
import { Http, storage, chunk } from "@mxweb/utils";
// Generic types
interface User {
id: number;
name: string;
}
const http = new Http("https://api.example.com");
const response = await http.get<User[]>("/users");
response.data; // Type: User[] | undefined
// Storage with types
storage.local.setItem<User>("user", { id: 1, name: "John" });
const user = storage.local.getItem<User>("user"); // Type: User | null
// Array utilities preserve types
const nums = [1, 2, 3, 4, 5];
const chunks = chunk(nums, 2); // Type: number[][]IDE Support
Full IntelliSense, autocomplete, type hints, and error checking in VS Code, WebStorm, and other TypeScript-aware IDEs.
Browser Support
@mxweb/utils supports all modern browsers and Node.js environments.
Modern Browsers
- โ Chrome 90+
- โ Firefox 88+
- โ Safari 14+
- โ Edge 90+
Mobile Browsers
- โ iOS Safari 14+
- โ Chrome for Android 90+
Node.js
- โ Node.js 14.x+
- โ Node.js 16.x LTS
- โ Node.js 18.x LTS
- โ Node.js 20.x LTS
Contributing
We welcome contributions! Please see our Contributing Guidelines for details.
Quick Start
- Fork the repository
- Install dependencies:
npm install - Make your changes
- Run tests:
npm test - Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog
See CHANGELOG.md for a detailed list of changes in each version.
Authors
- MxWeb Team - MxWeb.io
Acknowledgments
- Thanks to all contributors
- Inspired by Lodash, Axios, and modern utility libraries
- Built with TypeScript and bundled using Rollup
- Code quality maintained with ESLint and Prettier
- Tested with Vitest for reliability and stability
Made with โค๏ธ by MxWeb.IO
โญ Star us on GitHub โ it helps!