{"version":3,"sources":["../../src/utils/memoize.ts"],"sourcesContent":["// Copyright © Aptos Foundation\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Maximum number of entries to keep in the cache to prevent unbounded memory growth.\n * When this limit is exceeded, the oldest entries are evicted.\n */\nconst MAX_CACHE_SIZE = 1000;\n\n/**\n * Interval in milliseconds for periodic cleanup of expired cache entries.\n */\nconst CLEANUP_INTERVAL_MS = 60000; // 1 minute\n\n/**\n * Minimum required key length to help ensure uniqueness.\n * Keys should include a namespace prefix (e.g., \"module-abi-\", \"ledger-info-\").\n */\nconst MIN_KEY_LENGTH = 10;\n\n/**\n * Set of known cache key prefixes to help detect potential collisions.\n * Each caller should use a unique prefix.\n */\nconst knownKeyPrefixes = new Set<string>();\n\n/**\n * Validates a cache key for basic security requirements.\n * @param key - The cache key to validate\n * @throws Error if the key is invalid\n */\nfunction validateCacheKey(key: string): void {\n  if (!key || typeof key !== \"string\") {\n    throw new Error(\"Cache key must be a non-empty string\");\n  }\n  if (key.length < MIN_KEY_LENGTH) {\n    throw new Error(\n      `Cache key \"${key}\" is too short. Keys should be at least ${MIN_KEY_LENGTH} characters and include a namespace prefix.`,\n    );\n  }\n  // Extract prefix (everything before the last hyphen or the first 10 chars)\n  const prefixMatch = key.match(/^([a-z]+-[a-z]+-)/i);\n  if (prefixMatch) {\n    knownKeyPrefixes.add(prefixMatch[1]);\n  }\n}\n\n/**\n * Cache entry structure with value, timestamp, and last access time for LRU eviction.\n */\ninterface CacheEntry<T> {\n  value: T;\n  timestamp: number;\n  lastAccess: number;\n  ttlMs?: number;\n}\n\n/**\n * The global cache Map shared across all functions with LRU eviction support.\n * SECURITY: All cache keys should include a unique namespace prefix to prevent\n * collisions between different caching use cases (e.g., \"module-abi-\", \"ledger-info-\").\n * @group Implementation\n * @category Utils\n */\nconst cache = new Map<string, CacheEntry<any>>();\n\n/**\n * Track whether cleanup interval is set up.\n */\nlet cleanupIntervalId: ReturnType<typeof setInterval> | null = null;\n\n/**\n * Sets up periodic cleanup of expired cache entries if not already running.\n * This prevents memory leaks from accumulating expired entries.\n */\nfunction ensureCleanupInterval(): void {\n  if (cleanupIntervalId === null) {\n    cleanupIntervalId = setInterval(() => {\n      const now = Date.now();\n      const keysToDelete: string[] = [];\n\n      cache.forEach((entry, key) => {\n        // Remove entries that have expired\n        if (entry.ttlMs !== undefined && now - entry.timestamp > entry.ttlMs) {\n          keysToDelete.push(key);\n        }\n      });\n\n      keysToDelete.forEach((key) => cache.delete(key));\n\n      // If cache is empty, stop the cleanup interval to avoid unnecessary overhead\n      if (cache.size === 0 && cleanupIntervalId !== null) {\n        clearInterval(cleanupIntervalId);\n        cleanupIntervalId = null;\n      }\n    }, CLEANUP_INTERVAL_MS);\n\n    // Ensure the interval doesn't prevent Node.js from exiting\n    if (typeof cleanupIntervalId === \"object\" && \"unref\" in cleanupIntervalId) {\n      cleanupIntervalId.unref();\n    }\n  }\n}\n\n/**\n * Evicts the least recently used entries when cache exceeds maximum size.\n */\nfunction evictIfNeeded(): void {\n  if (cache.size >= MAX_CACHE_SIZE) {\n    // Find and remove the least recently accessed entries (remove ~10% of cache)\n    const entriesToRemove = Math.ceil(MAX_CACHE_SIZE * 0.1);\n    const entries = Array.from(cache.entries()).sort((a, b) => a[1].lastAccess - b[1].lastAccess);\n\n    for (let i = 0; i < entriesToRemove && i < entries.length; i += 1) {\n      cache.delete(entries[i][0]);\n    }\n  }\n}\n\n/**\n * A memoize higher-order function to cache the response of an async function.\n * This function helps to improve performance by avoiding repeated calls to the same async function with the same arguments\n * within a specified time-to-live (TTL).\n *\n * Features:\n * - LRU eviction when cache exceeds MAX_CACHE_SIZE entries\n * - Periodic cleanup of expired entries to prevent memory leaks\n *\n * @param func The async function to cache the result of.\n * @param key The cache key used to store the result.\n * @param ttlMs The time-to-live in milliseconds for cached data.\n * @returns The cached or latest result.\n * @group Implementation\n * @category Utils\n */\nexport function memoizeAsync<T>(\n  func: (...args: any[]) => Promise<T>,\n  key: string,\n  ttlMs?: number,\n): (...args: any[]) => Promise<T> {\n  // Validate key format on first call to catch misuse early\n  validateCacheKey(key);\n\n  return async (...args: any[]) => {\n    const now = Date.now();\n\n    // Check if the cached result exists and is within TTL\n    if (cache.has(key)) {\n      const entry = cache.get(key)!;\n      if (ttlMs === undefined || now - entry.timestamp <= ttlMs) {\n        // Update last access time for LRU\n        entry.lastAccess = now;\n        return entry.value;\n      }\n      // Entry expired, remove it\n      cache.delete(key);\n    }\n\n    // If not cached or TTL expired, compute the result\n    const result = await func(...args);\n\n    // Evict old entries if needed before adding new one\n    evictIfNeeded();\n\n    // Cache the result with a timestamp\n    cache.set(key, { value: result, timestamp: now, lastAccess: now, ttlMs });\n\n    // Ensure cleanup is running\n    ensureCleanupInterval();\n\n    return result;\n  };\n}\n\n/**\n * Caches the result of a function call to improve performance on subsequent calls with the same arguments.\n *\n * Features:\n * - LRU eviction when cache exceeds MAX_CACHE_SIZE entries\n * - Periodic cleanup of expired entries to prevent memory leaks\n *\n * @param key - The key to cache on, all accesses by this key will return the cached value.\n * @param func - The function whose result will be cached.\n * @param ttlMs - The time-to-live in milliseconds for cached data.\n * @returns A memoized version of the provided function that returns the cached result if available and within TTL.\n * @group Implementation\n * @category Utils\n */\nexport function memoize<T>(func: (...args: any[]) => T, key: string, ttlMs?: number): (...args: any[]) => T {\n  // Validate key format on first call to catch misuse early\n  validateCacheKey(key);\n\n  return (...args: any[]) => {\n    const now = Date.now();\n\n    // Check if the cached result exists and is within TTL\n    if (cache.has(key)) {\n      const entry = cache.get(key)!;\n      if (ttlMs === undefined || now - entry.timestamp <= ttlMs) {\n        // Update last access time for LRU\n        entry.lastAccess = now;\n        return entry.value;\n      }\n      // Entry expired, remove it\n      cache.delete(key);\n    }\n\n    // If not cached or TTL expired, compute the result\n    const result = func(...args);\n\n    // Evict old entries if needed before adding new one\n    evictIfNeeded();\n\n    // Cache the result with a timestamp\n    cache.set(key, { value: result, timestamp: now, lastAccess: now, ttlMs });\n\n    // Ensure cleanup is running\n    ensureCleanupInterval();\n\n    return result;\n  };\n}\n\n/**\n * Clears all entries from the memoization cache.\n * Useful for testing or when you need to force fresh data.\n * @group Implementation\n * @category Utils\n */\nexport function clearMemoizeCache(): void {\n  cache.clear();\n}\n\n/**\n * Returns the current size of the memoization cache.\n * Useful for monitoring and debugging.\n * @group Implementation\n * @category Utils\n */\nexport function getMemoizeCacheSize(): number {\n  return cache.size;\n}\n"],"mappings":"AAwBA,IAAMA,EAAmB,IAAI,IAO7B,SAASC,EAAiBC,EAAmB,CAC3C,GAAI,CAACA,GAAO,OAAOA,GAAQ,SACzB,MAAM,IAAI,MAAM,sCAAsC,EAExD,GAAIA,EAAI,OAAS,GACf,MAAM,IAAI,MACR,cAAcA,CAAG,uFACnB,EAGF,IAAMC,EAAcD,EAAI,MAAM,oBAAoB,EAC9CC,GACFH,EAAiB,IAAIG,EAAY,CAAC,CAAC,CAEvC,CAmBA,IAAMC,EAAQ,IAAI,IAKdC,EAA2D,KAM/D,SAASC,GAA8B,CACjCD,IAAsB,OACxBA,EAAoB,YAAY,IAAM,CACpC,IAAME,EAAM,KAAK,IAAI,EACfC,EAAyB,CAAC,EAEhCJ,EAAM,QAAQ,CAACK,EAAOP,IAAQ,CAExBO,EAAM,QAAU,QAAaF,EAAME,EAAM,UAAYA,EAAM,OAC7DD,EAAa,KAAKN,CAAG,CAEzB,CAAC,EAEDM,EAAa,QAASN,GAAQE,EAAM,OAAOF,CAAG,CAAC,EAG3CE,EAAM,OAAS,GAAKC,IAAsB,OAC5C,cAAcA,CAAiB,EAC/BA,EAAoB,KAExB,EAAG,GAAmB,EAGlB,OAAOA,GAAsB,UAAY,UAAWA,GACtDA,EAAkB,MAAM,EAG9B,CAKA,SAASK,GAAsB,CAC7B,GAAIN,EAAM,MAAQ,IAAgB,CAEhC,IAAMO,EAAkB,KAAK,KAAK,GAAoB,EAChDC,EAAU,MAAM,KAAKR,EAAM,QAAQ,CAAC,EAAE,KAAK,CAACS,EAAGC,IAAMD,EAAE,CAAC,EAAE,WAAaC,EAAE,CAAC,EAAE,UAAU,EAE5F,QAASC,EAAI,EAAGA,EAAIJ,GAAmBI,EAAIH,EAAQ,OAAQG,GAAK,EAC9DX,EAAM,OAAOQ,EAAQG,CAAC,EAAE,CAAC,CAAC,CAE9B,CACF,CAkBO,SAASC,EACdC,EACAf,EACAgB,EACgC,CAEhC,OAAAjB,EAAiBC,CAAG,EAEb,SAAUiB,IAAgB,CAC/B,IAAMZ,EAAM,KAAK,IAAI,EAGrB,GAAIH,EAAM,IAAIF,CAAG,EAAG,CAClB,IAAMO,EAAQL,EAAM,IAAIF,CAAG,EAC3B,GAAIgB,IAAU,QAAaX,EAAME,EAAM,WAAaS,EAElD,OAAAT,EAAM,WAAaF,EACZE,EAAM,MAGfL,EAAM,OAAOF,CAAG,CAClB,CAGA,IAAMkB,EAAS,MAAMH,EAAK,GAAGE,CAAI,EAGjC,OAAAT,EAAc,EAGdN,EAAM,IAAIF,EAAK,CAAE,MAAOkB,EAAQ,UAAWb,EAAK,WAAYA,EAAK,MAAAW,CAAM,CAAC,EAGxEZ,EAAsB,EAEfc,CACT,CACF,CAgBO,SAASC,EAAWJ,EAA6Bf,EAAagB,EAAuC,CAE1G,OAAAjB,EAAiBC,CAAG,EAEb,IAAIiB,IAAgB,CACzB,IAAMZ,EAAM,KAAK,IAAI,EAGrB,GAAIH,EAAM,IAAIF,CAAG,EAAG,CAClB,IAAMO,EAAQL,EAAM,IAAIF,CAAG,EAC3B,GAAIgB,IAAU,QAAaX,EAAME,EAAM,WAAaS,EAElD,OAAAT,EAAM,WAAaF,EACZE,EAAM,MAGfL,EAAM,OAAOF,CAAG,CAClB,CAGA,IAAMkB,EAASH,EAAK,GAAGE,CAAI,EAG3B,OAAAT,EAAc,EAGdN,EAAM,IAAIF,EAAK,CAAE,MAAOkB,EAAQ,UAAWb,EAAK,WAAYA,EAAK,MAAAW,CAAM,CAAC,EAGxEZ,EAAsB,EAEfc,CACT,CACF,CAQO,SAASE,GAA0B,CACxClB,EAAM,MAAM,CACd,CAQO,SAASmB,GAA8B,CAC5C,OAAOnB,EAAM,IACf","names":["knownKeyPrefixes","validateCacheKey","key","prefixMatch","cache","cleanupIntervalId","ensureCleanupInterval","now","keysToDelete","entry","evictIfNeeded","entriesToRemove","entries","a","b","i","memoizeAsync","func","ttlMs","args","result","memoize","clearMemoizeCache","getMemoizeCacheSize"]}