system-utils.tsā¢6.41 kB
// Removed duplicate import - using standard setTimeout
// Generic error handling wrapper
export async function withErrorHandling<T>(
operation: () => Promise<T>,
errorMessage: string
): Promise<T> {
try {
return await operation();
} catch (error) {
console.error(`${errorMessage}:`, error);
throw new Error(`${errorMessage}: ${error instanceof Error ? error.message : String(error)}`);
}
}
// Generic retry wrapper with exponential backoff (browser-agnostic version)
export async function withRetry<T>(
operation: () => Promise<T>,
options: {
maxRetries?: number;
delay?: number;
context?: string;
shouldRetry?: (error: Error, attempt: number) => boolean;
} = {}
): Promise<T> {
const {
maxRetries = 3,
delay = 1000,
context = 'unknown',
shouldRetry = () => true
} = options;
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
console.error(`Attempt ${attempt}/${maxRetries} failed in context ${context}:`, lastError.message);
// Check if we should retry this error
if (!shouldRetry(lastError, attempt) || attempt === maxRetries) {
break;
}
// Wait before retry with exponential backoff
const waitTime = delay * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
throw lastError!;
}
// Platform detection utilities
export function getPlatform(): NodeJS.Platform {
return process.platform;
}
export function isWindows(): boolean {
return process.platform === 'win32';
}
export function isMacOS(): boolean {
return process.platform === 'darwin';
}
export function isLinux(): boolean {
return process.platform === 'linux';
}
// Environment variable utilities
export function getEnvVar(name: string, defaultValue?: string): string | undefined {
return process.env[name] || defaultValue;
}
export function requireEnvVar(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Required environment variable ${name} is not set`);
}
return value;
}
// Process management utilities
export async function executeCommand(
command: string,
args: string[] = [],
options: {
timeout?: number;
encoding?: BufferEncoding;
stdio?: 'ignore' | 'inherit' | 'pipe';
} = {}
): Promise<string> {
const { execSync } = require('child_process');
const { timeout = 5000, encoding = 'utf8', stdio = 'pipe' } = options;
try {
const result = execSync(`${command} ${args.join(' ')}`, {
encoding,
timeout,
stdio
});
return result.toString();
} catch (error) {
throw new Error(`Command execution failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
// Sleep/delay utility
export async function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Timeout wrapper for any async operation
export async function withTimeout<T>(
operation: () => Promise<T>,
timeoutMs: number,
context: string = 'unknown'
): Promise<T> {
return new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Operation timed out after ${timeoutMs}ms in context: ${context}`));
}, timeoutMs);
operation()
.then((result) => {
clearTimeout(timer);
resolve(result);
})
.catch((error) => {
clearTimeout(timer);
reject(error);
});
});
}
// Generic validation utilities
export function validateRequired<T>(value: T | undefined | null, fieldName: string): T {
if (value === undefined || value === null) {
throw new Error(`Required field '${fieldName}' is missing`);
}
return value;
}
export function validateString(value: unknown, fieldName: string): string {
if (typeof value !== 'string') {
throw new Error(`Field '${fieldName}' must be a string, got ${typeof value}`);
}
return value;
}
export function validateNumber(value: unknown, fieldName: string): number {
if (typeof value !== 'number' || isNaN(value)) {
throw new Error(`Field '${fieldName}' must be a valid number, got ${typeof value}`);
}
return value;
}
export function validateBoolean(value: unknown, fieldName: string): boolean {
if (typeof value !== 'boolean') {
throw new Error(`Field '${fieldName}' must be a boolean, got ${typeof value}`);
}
return value;
}
// Object utilities
export function deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime()) as unknown as T;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item)) as unknown as T;
}
const cloned = {} as T;
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
export function isEmptyObject(obj: object): boolean {
return Object.keys(obj).length === 0;
}
// String utilities
export function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function slugify(str: string): string {
return str
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
// Array utilities
export function unique<T>(array: T[]): T[] {
return [...new Set(array)];
}
export function chunk<T>(array: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
// Type guards
export function isString(value: unknown): value is string {
return typeof value === 'string';
}
export function isNumber(value: unknown): value is number {
return typeof value === 'number' && !isNaN(value);
}
export function isBoolean(value: unknown): value is boolean {
return typeof value === 'boolean';
}
export function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
export function isArray<T>(value: unknown): value is T[] {
return Array.isArray(value);
}