Skip to main content
Glama

π“‚€π“’π“‹Ήπ”Έβ„•π•Œπ”Ήπ•€π•Šπ“‹Ήπ“’π“‚€ - Intelligent Guidance for

by Hive-Academy
execution-data.utils.tsβ€’6.39 kB
/** * Shared utilities for execution data handling * * Eliminates duplication between ExecutionAnalyticsService and ExecutionDataEnricherService * Provides type-safe data access and common calculations */ export class ExecutionDataUtils { /** * Safely get number property from execution object */ static safeGetNumber(obj: any, key: string, fallback: number): number { const value = obj?.[key]; return typeof value === 'number' ? value : fallback; } /** * Safely get string property from execution object */ static safeGetString(obj: any, key: string, fallback: string): string { const value = obj?.[key]; return typeof value === 'string' ? value : fallback; } /** * Safely get date property from execution object */ static safeGetDate(obj: any, key: string, fallback?: Date): Date { const value = obj?.[key]; return value instanceof Date ? value : fallback || new Date(); } /** * Safely get boolean property from execution object */ static safeGetBoolean(obj: any, key: string, fallback: boolean): boolean { const value = obj?.[key]; return typeof value === 'boolean' ? value : fallback; } /** * Round progress percentage to specified precision */ static roundProgress(progress: number, precision: number = 0): number { return ( Math.round(progress * Math.pow(10, precision)) / Math.pow(10, precision) ); } /** * Calculate duration between two dates */ static calculateDuration( start: Date, end: Date, options: { showSeconds?: boolean; showDays?: boolean; fallback?: string; } = {}, ): string { try { const diff = end.getTime() - start.getTime(); if (options.showDays) { const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor( (diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60), ); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); if (days > 0) { return `${days}d ${hours}h ${minutes}m`; } } const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); if (options.showSeconds) { const seconds = Math.floor((diff % (1000 * 60)) / 1000); return `${hours}h ${minutes}m ${seconds}s`; } return `${hours}h ${minutes}m`; } catch (_error) { return options.fallback || '0h 0m'; } } /** * Safely extract nested object property */ static safeGetNestedProperty( obj: any, path: string, fallback: any = null, ): any { try { const keys = path.split('.'); let current = obj; for (const key of keys) { if (current == null || typeof current !== 'object') { return fallback; } current = current[key]; } return current !== undefined ? current : fallback; } catch (_error) { return fallback; } } /** * Validate execution object has required properties */ static validateExecutionObject( obj: any, requiredFields: string[] = ['id'], ): boolean { if (!obj || typeof obj !== 'object') { return false; } return requiredFields.every((field) => obj[field] !== undefined); } /** * Calculate percentage with bounds checking */ static calculatePercentage( completed: number, total: number, precision: number = 0, ): number { if (total <= 0) return 0; if (completed >= total) return 100; if (completed <= 0) return 0; const percentage = (completed / total) * 100; return this.roundProgress(percentage, precision); } /** * Group array of objects by a property value */ static groupBy<T>( array: T[], keyExtractor: (item: T) => string, fallbackKey: string = 'unknown', ): Record<string, number> { return array.reduce((acc: Record<string, number>, item: T) => { try { const key = keyExtractor(item) || fallbackKey; acc[key] = (acc[key] || 0) + 1; return acc; } catch (_error) { acc[fallbackKey] = (acc[fallbackKey] || 0) + 1; return acc; } }, {}); } /** * Calculate average from array of numbers with safe extraction */ static calculateAverage<T>( array: T[], valueExtractor: (item: T) => number, fallback: number = 0, ): number { if (array.length === 0) return fallback; try { const sum = array.reduce((total: number, item: T) => { const value = valueExtractor(item); return total + (typeof value === 'number' ? value : 0); }, 0); return sum / array.length; } catch (_error) { return fallback; } } /** * Format time estimate for completion */ static formatTimeEstimate( totalSteps: number, completedSteps: number, averageStepDuration?: number, ): string | null { if (!totalSteps || totalSteps === 0) return null; const remaining = totalSteps - completedSteps; if (remaining <= 0) return 'Near completion'; if (averageStepDuration && averageStepDuration > 0) { const estimatedMinutes = remaining * averageStepDuration; const hours = Math.floor(estimatedMinutes / 60); const minutes = Math.round(estimatedMinutes % 60); if (hours > 0) { return `~${hours}h ${minutes}m remaining`; } return `~${minutes}m remaining`; } return `${remaining} steps remaining`; } /** * Calculate overall progress from multiple executions * * DEPENDENCY REDUCTION: Moved from ExecutionDataEnricherService to eliminate * circular dependency with ExecutionAnalyticsService. Both services can now * use this utility independently. */ static calculateOverallProgress<T>( executions: T[], progressExtractor: (execution: T) => number, fallbackProgress: number = 0, ): { averageProgress: number; totalActive: number } { const total = executions.length; if (total === 0) { return { averageProgress: fallbackProgress, totalActive: 0, }; } const averageProgress = this.calculateAverage( executions, progressExtractor, fallbackProgress, ); return { averageProgress: this.roundProgress(averageProgress, 0), totalActive: total, }; } }

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/Hive-Academy/Anubis-MCP'

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