helpers.tsā¢3.63 kB
/**
* Helper utilities
* Common utility functions used across the orchestrator
*/
/**
* Delay execution for specified milliseconds
*/
export function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Retry a function with exponential backoff
*/
export async function retry<T>(
fn: () => Promise<T>,
options: {
maxAttempts?: number;
baseDelay?: number;
maxDelay?: number;
backoffFactor?: number;
} = {}
): Promise<T> {
const {
maxAttempts = 3,
baseDelay = 1000,
maxDelay = 10000,
backoffFactor = 2,
} = options;
let lastError: Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (attempt === maxAttempts) {
throw lastError;
}
const delayMs = Math.min(
baseDelay * Math.pow(backoffFactor, attempt - 1),
maxDelay
);
await delay(delayMs);
}
}
throw lastError!;
}
/**
* Safely parse JSON with fallback
*/
export function safeJsonParse<T>(
json: string,
fallback: T
): T {
try {
return JSON.parse(json);
} catch {
return fallback;
}
}
/**
* Deep merge objects
*/
export function deepMerge<T extends Record<string, any>>(
target: T,
...sources: Partial<T>[]
): T {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
deepMerge(target[key] as Record<string, any>, source[key] as Record<string, any>);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return deepMerge(target, ...sources);
}
/**
* Check if value is an object
*/
function isObject(item: any): item is Record<string, any> {
return item && typeof item === 'object' && !Array.isArray(item);
}
/**
* Truncate string to specified length
*/
export function truncate(str: string, length: number, suffix = '...'): string {
if (str.length <= length) return str;
return str.substring(0, length - suffix.length) + suffix;
}
/**
* Generate a random ID
*/
export function generateId(prefix = '', length = 8): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = prefix;
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
/**
* Format duration in milliseconds to human readable string
*/
export function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
if (ms < 3600000) return `${(ms / 60000).toFixed(1)}m`;
return `${(ms / 3600000).toFixed(1)}h`;
}
/**
* Debounce function calls
*/
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
/**
* Throttle function calls
*/
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean;
return (...args: Parameters<T>) => {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}