@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.
491 lines (409 loc) โข 21.8 kB
Markdown
# @catbee/utils
## ๐งฐ Utility Modules for Node.js
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.
[build](https://github.com/catbee-technologies/catbee-utils/actions/workflows/node-build.yml/badge.svg)   
## ๐ฆ Installation
```bash
npm i @catbee/utils
```
## โก Quick Start
```ts
import { chunk, sleep, getLogger, uuid, isEmail } from "@catbee/utils";
// Chunk an array
const result = chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
// Sleep for 1 second
await sleep(1000);
// Log with context
getLogger().info("App started");
// Generate a secure UUID
console.log(uuid()); // e.g. 2a563ec1-caf6-4fe2-b60c-9cf7fb1bdb7f
// Basic validation
console.log(isEmail("user@example.com")); // true
```
## ๐ฆ Modules Overview
- [**Array Utilities**](#-array-utilities)
- [**Async Utilities**](#-async-utilities)
- [**Cache Utilities**](#-cache-utilities)
- [**Config**](#-config)
- [**Context Store**](#-context-store)
- [**Crypto Utilities**](#-crypto-utilities)
- [**Directory Utilities**](#-directory-utilities)
- [**Environment Utilities**](#-environment-utilities)
- [**Exception Utilities**](#-exception-utilities)
- [**File System Utilities**](#-file-system-utilities)
- [**HTTP Status Codes**](#-http-status-codes)
- [**ID Utilities**](#-id-utilities)
- [**Logger Utilities**](#-logger-utilities)
- [**Middleware Utilities**](#-middleware-utilities)
- [**Object Utilities**](#-object-utilities)
- [**Response Utilities**](#-response-utilities)
- [**String Utilities**](#-string-utilities)
- [**URL Utilities**](#-url-utilities)
- [**Validate Utilities**](#-validate-utilities)
- [**Decorators Utilities**](#decorators-utilities)
---
## ๐ฆ Array Utilities
- `chunk<T>(array: T[], size: number): T[][]` โ Split array into chunks.
- `unique<T>(array: T[], keyFn?: (item: T) => unknown): T[]` โ Remove duplicates.
- `flattenDeep<T>(array: any[]): T[]` โ Deep flatten nested arrays.
- `random<T>(array: T[]): T | undefined` โ Random element.
- `groupBy<T>(array: T[], keyOrFn: keyof T | ((item: T) => string | number | symbol)): Record<string | number | symbol, T[]>` โ Group by key.
- `shuffle<T>(array: T[]): T[]` โ Fisher-Yates shuffle.
- `pluck<T, K extends keyof T>(array: T[], key: K): T[K][]` โ Pluck values by key.
- `difference<T>(a: T[], b: T[]): T[]` โ Elements in `a` not in `b`.
- `intersect<T>(a: T[], b: T[]): T[]` โ Intersection.
- `mergeSort<T>(array: T[], key: string | ((item: T) => any), direction?: "asc" | "desc"): T[]` โ Merge sort by nested key.
- `zip<T>(...arrays: T[][]): T[][]` โ Zip arrays together.
- `partition<T>(array: T[], predicate: (item: T, index: number, array: T[]) => boolean): [T[], T[]]` โ Partition array by predicate.
- `range(start: number, end: number, step?: number): number[]` โ Generate a range of numbers.
- `take<T>(array: T[], n?: number): T[]` โ Take first `n` elements.
- `takeWhile<T>(array: T[], predicate: (item: T, index: number) => boolean): T[]` โ Take elements while predicate is true.
- `compact<T>(array: T[]): NonNullable<T>[]` โ Remove falsy values.
- `countBy<T>(array: T[], keyFn: (item: T) => string | number | symbol): Record<string, number>` โ Count occurrences by key.
- `sample<T>(array: T[], n?: number): T[]` โ Sample `n` elements.
## โณ Async Utilities
- `sleep(ms: number): Promise<void>`
- `debounce<T>(fn: T, delay: number): T & { cancel(): void; flush(): void }`
- `throttle<T>(fn: T, limit: number, opts?): (...args: Parameters<T>) => void`
- `retry<T>(fn: () => Promise<T>, retries?: number, delay?: number, backoff?: boolean, onRetry?): Promise<T>`
- `withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>`
- `runInBatches<T>(tasks: (() => Promise<T>)[], limit: number): Promise<T[]>`
- `singletonAsync<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>, drop?: boolean): (...args: TArgs) => Promise<TResult>`
- `settleAll<T>(tasks: (() => Promise<T>)[]): Promise<PromiseSettledResult<T>[]>`
- `createTaskQueue(limit: number): TaskQueue`
- `runInSeries<T>(tasks: (() => Promise<T>)[]): Promise<T[]>`
- `memoizeAsync<T, Args extends any[]>(fn: (...args: Args) => Promise<T>, options?): (...args: Args) => Promise<T>`
- `abortable<T>(promise: Promise<T>, signal: AbortSignal, abortValue?: any): Promise<T>`
- `createDeferred<T>(): [Promise<T>, (value: T | PromiseLike<T>) => void, (reason?: any) => void]`
- `waterfall<T>(fns: Array<(input: any) => Promise<any>>): (initialValue: any) => Promise<T>`
- `rateLimit<T>(fn: (...args: any[]) => Promise<T>, maxCalls: number, interval: number): (...args: any[]) => Promise<T>`
## ๐๏ธ Cache Utilities
- `TTLCache<K, V>(options?: TTLCacheOptions)` โ In-memory TTL cache with `.set`, `.get`, `.has`, `.delete`, `.clear`, `.cleanup`, `.entries`, `.keys`, `.values`, `.refresh`, `.stats`, `.destroy`, `.setMany`, `.getMany`, `.getOrCompute`.
**Example:**
```ts
const cache = new TTLCache<string, number>({ ttlMs: 3600_000 });
cache.set("foo", 42);
cache.get("foo"); // 42
cache.has("foo"); // true
cache.cleanup(); // cleans expired
```
## โ๏ธ Config
- `Config.Logger.level` โ Logging level (e.g., 'info', 'debug')
- `Config.Logger.name` โ Logger name
- `Config.Logger.isoTimestamp` โ Use ISO timestamps in logs
- `Config.Http.timeout` โ HTTP request timeout (ms)
- `Config.Cache.defaultTtl` โ Default cache TTL (seconds)
## ๐งฉ Context Store
- `ContextStore` โ Per-request context using AsyncLocalStorage.
**Static Methods:**
- `getInstance(): AsyncLocalStorage<Store>`
- `getAll(): Store | undefined` โ Get the current context store.
- `run(store: Store, callback: () => void): void`
- `set(key: symbol, value: unknown): void`
- `get<T>(key: symbol): T | undefined`
- `has(key: symbol): boolean`
- `delete(key: symbol): boolean`
- `patch(values: Partial<Record<symbol, unknown>>): void`
- `withValue(key: symbol, value: unknown, callback: () => T): T`
- `extend(newValues: Partial<Record<symbol, unknown>>, callback: () => T): T`
- `createExpressMiddleware(initialValuesFactory?): Middleware`
- `StoreKeys` โ Common context keys.
- `getRequestId(): string | undefined` โ Get the current request ID from context.
**Example (Express middleware usage):**
```ts
import { ContextStore, StoreKeys, getLogger } from "@catbee/utils";
import crypto from "crypto";
export function setupRequestContext(req, res, next) {
const requestId = req.headers["x-request-id"]?.toString() || crypto.randomUUID();
ContextStore.run({ [StoreKeys.REQUEST_ID]: requestId }, () => {
const logger = getLogger().child({ reqId: requestId });
ContextStore.set(StoreKeys.LOGGER, logger);
logger.info("Request context initialized");
next();
});
}
app.use(setupRequestContext);
```
## ๐ Crypto Utilities
- `hmac(algorithm: string, input: string, secret: string, encoding?: BinaryToTextEncoding): string`
- `hash(algorithm: string, input: string, encoding?: BinaryToTextEncoding): string`
- `sha256Hmac(input: string, secret: string): string`
- `sha1(input: string, encoding?: BinaryToTextEncoding): string`
- `sha256(input: string, encoding?: BinaryToTextEncoding): string`
- `md5(input: string): string`
- `randomString(): string`
- `generateRandomBytes(byteLength?: number): Buffer`
- `generateRandomBytesAsString(byteLength?: number, encoding?: BinaryToTextEncoding): string`
- `generateApiKey(prefix?: string, byteLength?: number): string`
- `safeCompare(a: string | Buffer | Uint8Array, b: string | Buffer | Uint8Array): boolean`
- `encrypt(data: string | Buffer, key: string | Buffer, options?): Promise<EncryptionResult>`
- `decrypt(encryptedData: EncryptionResult, key: string | Buffer, options?): Promise<string | Buffer>`
- `createSignedToken(payload: object, secret: string, expiresInSeconds?: number): string`
- `verifySignedToken(token: string, secret: string): object | null`
## ๐ Directory Utilities
- `ensureDir(dirPath: string): Promise<void>`
- `listFiles(dirPath: string, recursive?: boolean): Promise<string[]>`
- `deleteDirRecursive(dirPath: string): Promise<void>`
- `isDirectory(pathStr: string): Promise<boolean>`
- `copyDir(src: string, dest: string): Promise<void>`
- `moveDir(src: string, dest: string): Promise<void>`
- `emptyDir(dirPath: string): Promise<void>`
- `getDirSize(dirPath: string): Promise<number>`
- `watchDir(dirPath: string, callback): () => void`
- `findFilesByPattern(pattern: string, options?): Promise<string[]>`
- `getSubdirectories(dirPath: string, recursive?: boolean): Promise<string[]>`
- `ensureEmptyDir(dirPath: string): Promise<void>`
- `createTempDir(options?): Promise<{ path: string, cleanup: () => Promise<void> }>`
- `findNewestFile(dirPath: string, recursive?: boolean): Promise<string | null>`
- `findOldestFile(dirPath: string, recursive?: boolean): Promise<string | null>`
- `findInDir(dirPath: string, predicate, recursive?: boolean): Promise<string[]>`
- `watchDirRecursive(dirPath: string, callback, includeSubdirs?: boolean): Promise<() => void>`
- `getDirStats(dirPath: string): Promise<{ fileCount: number, dirCount: number, totalSize: number }>`
- `walkDir(dirPath: string, options): Promise<void>`
## ๐ฑ Environment Utilities
- `Environment` enum โ (`DEVELOPMENT`, `PRODUCTION`, `STAGING`, `TESTING`)
- `Env` class โ Environment variable helpers.
**Static Methods:**
- `isDev(): boolean`
- `isProd(): boolean`
- `isTest(): boolean`
- `isStaging(): boolean`
- `set(key: string, value: string): void`
- `getAll(): object`
- `get(key: string, defaultValue?: string): string | undefined`
- `getRequired(key: string): string`
- `getNumber(key: string, defaultValue: number): number`
- `getNumberRequired(key: string): number`
- `getBoolean(key: string, defaultValue?: boolean): boolean`
- `getBooleanRequired(key: string): boolean`
- `getJSON<T>(key: string, defaultValue: T): T`
- `getArray<T = string>(key: string, defaultValue?: T[], splitter?: string): string[] | T[]`
- `getEnum<T extends string>(key: string, allowedValues: T[], defaultValue?: T): T`
- `getUrl(key: string, defaultValue?: string, options?): string`
- `getEmail(key: string, defaultValue?: string): string`
- `getPath(key: string, defaultValue?: string, options?): string`
- `getPort(key: string, defaultValue?: number): number`
- `getDuration(key: string, defaultValue?: string | number): number`
- `getSafeEnv(sensitiveKeys?: string[]): Record<string, string>`
- `getWithDefault(key: string, defaultFn: () => string): string`
- `has(key: string): boolean`
- `delete(key: string): void`
## ๐จ Exception Utilities
- `HttpError`, `InternalServerErrorException`, `UnauthorizedException`, `BadRequestException`, `NotFoundException`, `ForbiddenException`, `ConflictException`, `BadGatewayException`, `TooManyRequestsException`, `ServiceUnavailableException`, `GatewayTimeoutException`, `UnprocessableEntityException`, `MethodNotAllowedException`, `NotAcceptableException`, `RequestTimeoutException`, `UnsupportedMediaTypeException`, `PayloadTooLargeException`, `InsufficientStorageException`
- `isHttpError(error: unknown): error is ErrorResponse`
- `createHttpError(status: number, message?: string): ErrorResponse`
- `hasErrorShape(error: unknown): error is { message: string; status?: number; code?: string }`
- `getErrorMessage(error: unknown): string`
- `withErrorHandling<T extends (...args: any[]) => Promise<any>>(handler: T): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>>`
## ๐ File System Utilities
- `fileExists(path: string): Promise<boolean>`
- `readJsonFile<T>(path: string): Promise<T | null>`
- `writeJsonFile(path: string, data: any, space?: number): Promise<void>`
- `deleteFileIfExists(path: string): Promise<boolean>`
- `readTextFile(path: string, encoding?: BufferEncoding): Promise<string | null>`
- `writeTextFile(path: string, content: string, encoding?: BufferEncoding): Promise<boolean>`
- `appendTextFile(path: string, content: string, encoding?: BufferEncoding): Promise<boolean>`
- `copyFile(source: string, destination: string, overwrite?: boolean): Promise<boolean>`
- `moveFile(oldPath: string, newPath: string): Promise<boolean>`
- `getFileStats(path: string): Promise<fs.Stats | null>`
- `createTempFile(options?): Promise<string>`
- `streamFile(source: string, destination: string): Promise<void>`
- `readDirectory(dirPath: string, options?): Promise<string[]>`
- `createDirectory(dirPath: string, recursive?: boolean): Promise<boolean>`
- `safeReadJsonFile<T>(path: string): Promise<{ data: T | null; error: Error | null }>`
- `isFile(path: string): Promise<boolean>`
- `getFileSize(path: string): Promise<number>`
- `readFileBuffer(path: string): Promise<Buffer | null>`
## ๐ HTTP Status Codes
A typed enum with all HTTP status codes and their standard messages.
- `HttpStatusCodes.OK === 200`
- `HttpStatusCodes.NOT_FOUND === 404`
- ...and so on.
**Example:**
```ts
import { HttpStatusCodes } from "@catbee/utils";
res.status(HttpStatusCodes.BAD_REQUEST).send("Invalid payload");
```
## ๐ ID Utilities
- `uuid(): string`
- `nanoId(size?: number): string`
- `randomHex(byteLength?: number): string`
- `randomInt(min: number, max: number): number`
- `randomBase64(byteLength?: number): number`
## ๐ Logger Utilities
- `getLogger(): Logger`
- `createChildLogger(bindings: Record<string, any>, parentLogger?: Logger): Logger`
- `createRequestLogger(requestId: string, additionalContext?: Record<string, any>): Logger`
- `logError(error: Error | unknown, message?: string, context?: Record<string, any>): void`
- `resetLogger(): void`
## ๐งฉ Middleware Utilities
- `requestId(options?): Middleware`
- `responseTime(options?): Middleware`
- `timeout(timeoutMs?: number): Middleware`
- `errorHandler(options?): Middleware`
- `cors(options?): Middleware`
- `validateRequest(schema, location?): Middleware`
- `rateLimit(options?): Middleware`
- `securityHeaders(options?): Middleware`
- `basicAuth(validator, realm?): Middleware`
**Example:**
```ts
import { requestId, responseTime, errorHandler } from "@catbee/utils";
app.use(requestId());
app.use(responseTime());
app.use(errorHandler());
```
## ๐งฉ Object Utilities
- `isObjEmpty(obj: Record<any, any>): boolean`
- `pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>`
- `omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>`
- `deepObjMerge<T>(target: T, source: Partial<T>): T`
- `flattenObject<T>(obj: T, prefix?: string): Record<string, any>`
- `getValueByPath<T>(obj: T, path: string): any`
- `setValueByPath<T>(obj: T, path: string, value: any): T`
- `deepClone<T>(obj: T): T`
- `unflattenObject(obj: Record<string, any>): Record<string, any>`
- `isEqual(a: any, b: any): boolean`
- `filterObject<T>(obj: T, predicate): Partial<T>`
- `mapObject<T, U>(obj: T, mapFn): Record<keyof T, U>`
- `deepFreeze<T>(obj: T): Readonly<T>`
- `isObject(value: unknown): value is Record<string, any>`
- `getAllPaths(obj: Record<string, any>, parentPath?: string): string[]`
## ๐ Response Utilities
- `SuccessResponse<T>` โ Standard API success wrapper.
- `ErrorResponse` โ Standard API error wrapper.
- `PaginatedResponse<T>` โ Paginated API response.
- `NoContentResponse` โ 204 No Content response.
- `RedirectResponse` โ Redirect response.
- `createSuccessResponse<T>(data: T, message?: string): SuccessResponse<T>`
- `createErrorResponse(message: string, statusCode?: number): ErrorResponse`
- `createPaginatedResponse<T>(allItems: T[], page: number, pageSize: number, message?: string): PaginatedResponse<T>`
- `sendResponse(res, apiResponse): void`
- `isApiResponse(value: any): value is ApiResponse<any>`
## ๐งต String Utilities
- `capitalize(str: string): string`
- `toKebabCase(str: string): string`
- `toCamelCase(str: string): string`
- `slugify(str: string): string`
- `truncate(str: string, len: number): string`
- `toPascalCase(str: string): string`
- `toSnakeCase(str: string): string`
- `format(template: string, values: Record<string, any> | any[]): string`
- `isValidEmail(str: string): boolean`
- `isValidUrl(str: string, requireProtocol?: boolean): boolean`
- `mask(str: string, visibleStart?: number, visibleEnd?: number, maskChar?: string): string`
- `stripHtml(str: string): string`
- `equalsIgnoreCase(a: string, b: string): boolean`
- `reverse(str: string): string`
- `countOccurrences(str: string, substring: string, caseSensitive?: boolean): number`
- `randomString(length?: number, charset?: string): string`
- `pluralize(singular: string, count: number, plural?: string): string`
- `toTitleCase(str: string): string`
- `pad(str: string, length: number, padChar?: string, padEnd?: boolean): string`
## ๐ URL Utilities
- `appendQueryParams(url: string, params: Record<string, string | number>): string`
- `parseQueryString(query: string): Record<string, string>`
- `isValidUrl(url: string, requireHttps?: boolean): boolean`
- `getDomain(url: string, removeSubdomains?: boolean): string`
- `joinPaths(...segments: string[]): string`
- `normalizeUrl(url: string, base?: string): string`
- `createUrlBuilder(baseUrl: string): { path(path: string, params?: Record<string, any>): string; query(params: Record<string, any>): string }`
- `extractQueryParams(url: string, paramNames: string[]): Record<string, string>`
- `removeQueryParams(url: string, paramsToRemove: string[]): string`
- `getExtension(url: string): string`
- `parseTypedQueryParams<T>(url: string, converters?: Record<keyof T, (val: string) => any>): Partial<T>`
**Example:**
```ts
const url = appendQueryParams('https://example.com', { page: 1, limit: 10 });
// โ 'https://example.com/?page=1&limit=10'
```
## โ
Validate Utilities
A comprehensive suite of string/format validators for safe input and API checks.
- `isEmail(str: string): boolean`
- `isUUID(str: string): boolean`
- `isURL(str: string): boolean`
- `isPhone(str: string): boolean`
- `isAlphanumeric(str: string): boolean`
- `isNumeric(value: string | number): boolean`
- `isHexColor(str: string): boolean`
- `isISODate(str: string): boolean`
- `isLengthBetween(str: string, min: number, max: number): boolean`
- `isNumberBetween(value: number, min: number, max: number): boolean`
- `isAlpha(str: string): boolean`
- `isStrongPassword(str: string): boolean`
- `isIPv4(str: string): boolean`
- `isIPv6(str: string): boolean`
- `isCreditCard(str: string): boolean`
- `isValidJSON(str: string): boolean`
- `isObject(value: unknown): value is Record<string, unknown>`
- `isArray<T = unknown>(value: unknown, itemGuard?: (item: unknown) => item is T): value is T[]`
- `isBase64(str: string): boolean`
- `hasRequiredProps(obj: Record<string, unknown>, requiredProps: string[]): boolean`
- `isDateInRange(date: Date, minDate?: Date, maxDate?: Date): boolean`
- `matchesPattern(str: string, pattern: RegExp): boolean`
- `validateAll(value: unknown, validators: Array<(value: unknown) => boolean>): boolean`
---
# ๐ง Decorators Utilities
## Available Decorators
- `@Controller(basePath: string)` โ Class decorator to set the base route.
- `@Get(path)`, `@Post(path)`, `@Put(path)`, `@Patch(path)`, `@Delete(path)`, `@Options(path)`, `@Head(path)`, `@Trace(path)`, `@Connect(path)` โ Method decorators for HTTP verbs.
- `@Use(...middlewares)` โ Attach Express-style middleware to a route handler.
- `@Query(key?)`, `@Param(key?)`, `@Body(key?)`, `@Req()`, `@Res()` โ Parameter decorators for extracting request data.
- `@HttpCode(status)` โ Set custom HTTP status code for the response.
- `@Header(name, value)` โ Set custom response headers.
- `@Before(fn)`, `@After(fn)` โ Register before/after hooks for a route handler.
## Example Usage
```typescript
import {
Controller, Get, Post, Use, Query, Param, Body, Req, Res,
HttpCode, Header, Before, After, registerControllers, Request, Response, NextFunction
} from './src/utils/decorators.utils';
// Example middleware
function logMiddleware(req: Request, res: Response, next: NextFunction) {
console.log('Request:', req.method, req.url);
next();
}
// Example before/after hooks
function beforeHook(req: Request, res: Response) {
console.log('Before handler');
}
function afterHook(req: Request, res: Response, result: any) {
console.log('After handler', result);
}
@Controller('/api')
class ExampleController {
@Get('/items/:id')
@Use(logMiddleware)
@HttpCode(200)
@Header('X-Example', 'yes')
@Before(beforeHook)
@After(afterHook)
getItem(
@Query('q') q: string,
@Param('id') id: string,
@Body('name') name: string,
@Req() req: Request,
@Res() res: Response
) {
return { q, id, name };
}
@Post('/items')
createItem(@Body() body: any) {
return { created: true, ...body };
}
}
// Register controllers with your router (Express-like)
const router = /* your router instance */;
registerControllers(router, [ExampleController]);
```
## Notes
- Decorated methods **must** use standard method syntax, not arrow functions or property initializers.
- All parameter decorators (`@Query`, `@Param`, etc.) are optional and can be used in any order.
- `registerControllers(router, controllers)` will register all routes and apply middlewares, hooks, status codes, and headers as defined.
## ๐ Usage
Import only what you need:
```ts
import { chunk, sleep, TTLCache, getLogger } from "@catbee/utils";
```
## ๐ License
MIT ยฉ catbee-technologies