/**
* Utility functions for string manipulation
*/
export function capitalize(str: string): string {
if (!str) return "";
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, "")
.replace(/[\s_-]+/g, "-")
.replace(/^-+|-+$/g, "");
}
/**
* Validates email format
*/
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Debounce function to limit execution rate
*/
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
return function executedFunction(...args: Parameters<T>) {
const later = () => {
timeout = null;
func(...args);
};
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(later, wait);
};
}
/**
* Deep clone an object
*/
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 any;
}
if (Array.isArray(obj)) {
const clonedArr: any[] = [];
for (let i = 0; i < obj.length; i++) {
clonedArr[i] = deepClone(obj[i]);
}
return clonedArr as any;
}
if (obj instanceof Object) {
const clonedObj: any = {};
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
return obj;
}
/**
* Retry a function with exponential backoff
*/
export async function retry<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (attempt < maxRetries) {
const backoffDelay = delay * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, backoffDelay));
}
}
}
throw lastError!;
}