@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
184 lines • 7.22 kB
JavaScript
/**
* 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