UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

184 lines 7.22 kB
/** * Safe ID String Converter * * Handles conversion of numeric IDs to strings without scientific notation. * JavaScript's Number type can only safely represent integers up to 2^53 - 1. * Large IDs from APIs can exceed this and get converted to scientific notation * when using String() or toString(), causing duplicate records in the database. * * Example problem: * - API returns: 5600699287863296 * - String(id): "5.6006992878633e+15" ❌ * - safeIdToString(id): "5600699287863296" ✅ */ import { getLogger } from '../logging/Logger.js'; /** * Safely converts any ID value to a string without scientific notation * @param id - The ID to convert (can be string, number, bigint, or object with toString) * @returns String representation of the ID without scientific notation */ export function safeIdToString(id) { // Already a string - check for .0 suffix and remove it only for numeric strings if (typeof id === 'string') { // First check if it contains scientific notation if (id.includes('e+') || id.includes('E+') || id.includes('e-') || id.includes('E-')) { return extractFromScientificNotation(id); } // Remove trailing .0 only if the string represents a number if (id.endsWith('.0') && /^\d+\.0$/.test(id)) { return id.slice(0, -2); } return id; } // Handle null/undefined if (id == null) { getLogger().warn({ id }, 'SafeIdConverter: Received null/undefined ID'); return ''; } // Handle numbers if (typeof id === 'number') { // Check if it's beyond JavaScript's safe integer range if (Math.abs(id) > Number.MAX_SAFE_INTEGER) { try { // Use BigInt for precise conversion - use Math.floor to handle floats const bigIntId = BigInt(Math.floor(id)); const result = bigIntId.toString(); getLogger().debug({ originalId: id, scientificNotation: String(id), safeConversion: result }, 'SafeIdConverter: Converted large number ID using BigInt'); // Remove trailing .0 if present if (result.endsWith('.0')) { return result.slice(0, -2); } return result; } catch (error) { // Fallback if BigInt conversion fails getLogger().error({ id, error: error instanceof Error ? error.message : String(error) }, 'SafeIdConverter: BigInt conversion failed, using fallback'); // Try to extract from scientific notation return extractFromScientificNotation(String(id)); } } // Safe to convert normally - but check for .0 suffix const result = id.toString(); // Remove trailing .0 if present (happens when integers are stored as floats) if (result.endsWith('.0')) { return result.slice(0, -2); } return result; } // Handle BigInt if (typeof id === 'bigint') { const result = id.toString(); // Remove trailing .0 if present (shouldn't happen with BigInt but be safe) if (result.endsWith('.0')) { return result.slice(0, -2); } return result; } // Handle objects with numeric value if (typeof id === 'object' && id !== null) { // Check for numeric value property (some APIs return {value: 123}) if ('value' in id && (typeof id.value === 'number' || typeof id.value === 'string')) { return safeIdToString(id.value); } // Check for id property if ('id' in id && (typeof id.id === 'number' || typeof id.id === 'string')) { return safeIdToString(id.id); } } // Fallback - convert to string let result = String(id); // Check if we ended up with scientific notation if (result.includes('e+') || result.includes('E+')) { getLogger().warn({ id, result }, 'SafeIdConverter: Fallback conversion resulted in scientific notation'); result = extractFromScientificNotation(result); } // Remove trailing .0 if present if (result.endsWith('.0')) { return result.slice(0, -2); } return result; } /** * Attempts to extract the original number from scientific notation * @param scientificStr - String in scientific notation (e.g., "5.6006992878633e+15") * @returns Best effort conversion to regular number string */ function extractFromScientificNotation(scientificStr) { try { // Parse the scientific notation const num = parseFloat(scientificStr); if (isNaN(num)) { return scientificStr; } // For very large numbers, we lose precision with parseFloat // This is a best-effort recovery if (Math.abs(num) > Number.MAX_SAFE_INTEGER) { // Try to reconstruct using the components const match = scientificStr.match(/^([+-]?\d*\.?\d+)[eE]([+-]?\d+)$/); if (match) { const [, mantissa, exponent] = match; const exp = parseInt(exponent, 10); // This is approximate due to floating point limitations getLogger().warn({ scientificStr, mantissa, exponent: exp, warning: 'Precision may be lost in conversion' }, 'SafeIdConverter: Attempting to recover from scientific notation'); } } // Convert to BigInt if possible for better precision try { const bigIntValue = BigInt(Math.floor(num)); // Use Math.floor to remove decimals const result = bigIntValue.toString(); // Remove trailing .0 if present if (result.endsWith('.0')) { return result.slice(0, -2); } return result; } catch { // If BigInt fails, return the number as string const result = Math.floor(num).toString(); // Remove trailing .0 if present if (result.endsWith('.0')) { return result.slice(0, -2); } return result; } } catch (error) { getLogger().error({ scientificStr, error: error instanceof Error ? error.message : String(error) }, 'SafeIdConverter: Failed to extract from scientific notation'); return scientificStr; } } /** * Validates if an ID string contains scientific notation * @param idStr - The ID string to check * @returns true if the string contains scientific notation */ export function hasScientificNotation(idStr) { return /[eE][+-]?\d+/.test(idStr); } /** * Batch converts an array of IDs to safe strings * @param ids - Array of IDs to convert * @returns Array of string IDs without scientific notation */ export function safeIdArrayToStrings(ids) { return ids.map(id => safeIdToString(id)); } //# sourceMappingURL=SafeIdConverter.js.map