Skip to main content
Glama
index.ts6.5 kB
import { Logger } from '@nestjs/common'; const logger = new Logger('Utils'); /** * Sleep for a specified number of milliseconds */ export function sleep(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>, maxAttempts: number = 3, initialDelay: number = 1000, maxDelay: number = 10000 ): Promise<T> { let lastError: Error; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (attempt === maxAttempts) { throw lastError; } const delay = Math.min(initialDelay * Math.pow(2, attempt - 1), maxDelay); logger.warn(`Attempt ${attempt} failed, retrying in ${delay}ms: ${lastError.message}`); await sleep(delay); } } throw lastError!; } /** * Create a timeout promise that rejects after specified milliseconds */ export function timeout<T>(promise: Promise<T>, ms: number): Promise<T> { return Promise.race([ promise, new Promise<never>((_, reject) => { setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms); }) ]); } /** * Validate that a string is a valid URL */ export function isValidUrl(url: string): boolean { try { new URL(url); return true; } catch { return false; } } /** * Sanitize a string to be used as a valid identifier */ export function sanitizeIdentifier(str: string): string { return str .replace(/[^a-zA-Z0-9_]/g, '_') .replace(/^[0-9]/, '_$&') .replace(/_+/g, '_') .replace(/^_|_$/g, ''); } /** * Generate a unique ID */ export function generateId(prefix: string = 'id'): string { const timestamp = Date.now(); const random = Math.random().toString(36).substr(2, 9); return `${prefix}_${timestamp}_${random}`; } /** * 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 unknown as T; } if (obj instanceof Array) { return obj.map(item => deepClone(item)) as unknown as T; } if (typeof obj === 'object') { const cloned = {} as T; for (const key in obj) { if (obj.hasOwnProperty(key)) { cloned[key] = deepClone(obj[key]); } } return cloned; } return obj; } /** * Merge objects deeply */ 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], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return deepMerge(target, ...sources); } /** * Check if value is a plain object */ export function isObject(item: any): item is Record<string, any> { return item && typeof item === 'object' && !Array.isArray(item); } /** * Convert bytes to human readable format */ export function formatBytes(bytes: number, decimals: number = 2): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } /** * Format duration in milliseconds to human readable format */ export function formatDuration(ms: number): string { if (ms < 1000) { return `${ms}ms`; } const seconds = Math.floor(ms / 1000); if (seconds < 60) { return `${seconds}s`; } const minutes = Math.floor(seconds / 60); if (minutes < 60) { return `${minutes}m ${seconds % 60}s`; } const hours = Math.floor(minutes / 60); return `${hours}h ${minutes % 60}m`; } /** * Capitalize first letter of a string */ export function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } /** * Convert camelCase to kebab-case */ export function camelToKebab(str: string): string { return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); } /** * Convert kebab-case to camelCase */ export function kebabToCamel(str: string): string { return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); } /** * Truncate string to specified length */ export function truncate(str: string, length: number, suffix: string = '...'): string { if (str.length <= length) { return str; } return str.substring(0, length - suffix.length) + suffix; } /** * Remove undefined values from object */ export function removeUndefined<T extends Record<string, any>>(obj: T): Partial<T> { const result: Partial<T> = {}; for (const key in obj) { if (obj[key] !== undefined) { result[key] = obj[key]; } } return result; } /** * Parse JSON safely with error handling */ export function parseJson<T = any>(jsonString: string): { success: true; data: T } | { success: false; error: string } { try { const data = JSON.parse(jsonString); return { success: true, data }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Invalid JSON' }; } } /** * Create a debounced function */ 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); }; } /** * Create a throttled function */ export function throttle<T extends (...args: any[]) => any>( func: T, limit: number ): (...args: Parameters<T>) => void { let lastFunc: NodeJS.Timeout; let lastRan: number; return (...args: Parameters<T>) => { if (!lastRan) { func(...args); lastRan = Date.now(); } else { clearTimeout(lastFunc); lastFunc = setTimeout(() => { if ((Date.now() - lastRan) >= limit) { func(...args); lastRan = Date.now(); } }, limit - (Date.now() - lastRan)); } }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zaizaizhao/mcp-swagger-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server