@socketsecurity/lib 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.1.1](https://github.com/SocketDev/socket-lib/releases/tag/v3.1.1) - 2025-11-02
9
+
10
+ ### Fixed
11
+
12
+ - **Cache TTL**: Fixed flaky test by handling persistent cache write failures gracefully
13
+ - Wrapped `cacache.put` in try/catch to prevent failures when persistent cache writes fail or are slow
14
+ - In-memory cache is updated synchronously before the persistent write, so immediate reads succeed regardless of persistent cache state
15
+ - Improves reliability in test environments and when cache directory has issues
16
+
8
17
  ## [3.1.0](https://github.com/SocketDev/socket-lib/releases/tag/v3.1.0) - 2025-11-01
9
18
 
10
19
  ### Changed
@@ -162,9 +162,12 @@ function createTtlCache(options) {
162
162
  if (opts.memoize) {
163
163
  memoCache.set(fullKey, entry);
164
164
  }
165
- await cacache.put(fullKey, JSON.stringify(entry), {
166
- metadata: { expiresAt: entry.expiresAt }
167
- });
165
+ try {
166
+ await cacache.put(fullKey, JSON.stringify(entry), {
167
+ metadata: { expiresAt: entry.expiresAt }
168
+ });
169
+ } catch {
170
+ }
168
171
  }
169
172
  async function getOrFetch(key, fetcher) {
170
173
  const cached = await get(key);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/cache-with-ttl.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Generic TTL-based caching utility using cacache.\n *\n * Provides a simple interface for caching data with time-to-live (TTL) expiration.\n * Uses cacache for persistent storage with metadata for TTL tracking.\n *\n * Features:\n * - Automatic expiration based on TTL\n * - In-memory memoization for hot data\n * - Persistent storage across process restarts\n * - Type-safe with generics\n *\n * Usage:\n * ```ts\n * const cache = createTtlCache({ ttl: 5 * 60 * 1000 }) // 5 minutes\n * const data = await cache.getOrFetch('key', async () => fetchData())\n * ```\n */\n\nimport * as cacache from './cacache'\n\nexport interface TtlCacheOptions {\n /**\n * Time-to-live in milliseconds.\n * @default 5 * 60 * 1000 (5 minutes)\n */\n ttl?: number | undefined\n /**\n * Enable in-memory memoization for hot data.\n * @default true\n */\n memoize?: boolean | undefined\n /**\n * Custom cache key prefix.\n * Must not contain wildcards (*).\n * Use clear({ prefix: \"pattern*\" }) for wildcard matching instead.\n *\n * @default 'ttl-cache'\n * @throws {TypeError} If prefix contains wildcards\n *\n * @example\n * // Valid\n * createTtlCache({ prefix: 'socket-sdk' })\n * createTtlCache({ prefix: 'my-app:cache' })\n *\n * @example\n * // Invalid - throws TypeError\n * createTtlCache({ prefix: 'socket-*' })\n */\n prefix?: string | undefined\n}\n\nexport interface TtlCacheEntry<T> {\n data: T\n expiresAt: number\n}\n\nexport interface ClearOptions {\n /**\n * Only clear in-memory memoization cache, not persistent cache.\n * Useful for forcing a refresh of cached data without removing it from disk.\n *\n * @default false\n */\n memoOnly?: boolean | undefined\n}\n\nexport interface TtlCache {\n /**\n * Get cached data without fetching.\n * Returns undefined if not found or expired.\n *\n * @param key - Cache key (must not contain wildcards)\n * @throws {TypeError} If key contains wildcards (*)\n */\n get<T>(key: string): Promise<T | undefined>\n /**\n * Get all cached entries matching a pattern.\n * Supports wildcards (*) for flexible matching.\n *\n * @param pattern - Key pattern (supports * wildcards, or use '*' for all entries)\n * @returns Map of matching entries (key -> value)\n *\n * @example\n * // Get all organization entries\n * const orgs = await cache.getAll<OrgData>('organizations:*')\n * for (const [key, org] of orgs) {\n * console.log(`${key}: ${org.name}`)\n * }\n *\n * @example\n * // Get all entries with this cache's prefix\n * const all = await cache.getAll<any>('*')\n */\n getAll<T>(pattern: string): Promise<Map<string, T>>\n /**\n * Get cached data or fetch and cache if missing/expired.\n *\n * @param key - Cache key (must not contain wildcards)\n */\n getOrFetch<T>(key: string, fetcher: () => Promise<T>): Promise<T>\n /**\n * Set cached data with TTL.\n *\n * @param key - Cache key (must not contain wildcards)\n * @throws {TypeError} If key contains wildcards (*)\n */\n set<T>(key: string, data: T): Promise<void>\n /**\n * Delete a specific cache entry.\n *\n * @param key - Cache key (must not contain wildcards)\n * @throws {TypeError} If key contains wildcards (*)\n */\n delete(key: string): Promise<void>\n /**\n * Delete all cache entries matching a pattern.\n * Supports wildcards (*) for flexible matching.\n *\n * @param pattern - Key pattern (supports * wildcards, or omit to delete all)\n * @returns Number of entries deleted\n *\n * @example\n * // Delete all entries with this cache's prefix\n * await cache.deleteAll()\n *\n * @example\n * // Delete entries matching prefix\n * await cache.deleteAll('organizations')\n *\n * @example\n * // Delete entries with wildcard pattern\n * await cache.deleteAll('scans:abc*')\n * await cache.deleteAll('npm/lodash/*')\n */\n deleteAll(pattern?: string | undefined): Promise<number>\n /**\n * Clear all cache entries (like Map.clear()).\n * Optionally clear only in-memory cache.\n *\n * @param options - Optional configuration\n * @param options.memoOnly - If true, only clears in-memory cache\n *\n * @example\n * // Clear everything (memory + disk)\n * await cache.clear()\n *\n * @example\n * // Clear only in-memory cache (force refresh)\n * await cache.clear({ memoOnly: true })\n */\n clear(options?: ClearOptions | undefined): Promise<void>\n}\n\n// 5 minutes\nconst DEFAULT_TTL_MS = 5 * 60 * 1000\nconst DEFAULT_PREFIX = 'ttl-cache'\n\n/**\n * Create a TTL-based cache instance.\n */\nexport function createTtlCache(options?: TtlCacheOptions): TtlCache {\n const opts = {\n __proto__: null,\n memoize: true,\n prefix: DEFAULT_PREFIX,\n ttl: DEFAULT_TTL_MS,\n ...options,\n } as Required<TtlCacheOptions>\n\n // Validate prefix does not contain wildcards.\n if (opts.prefix?.includes('*')) {\n throw new TypeError(\n 'Cache prefix cannot contain wildcards (*). Use clear({ prefix: \"pattern*\" }) for wildcard matching.',\n )\n }\n\n // In-memory cache for hot data\n const memoCache = new Map<string, TtlCacheEntry<any>>()\n\n // Ensure ttl is defined\n const ttl = opts.ttl ?? DEFAULT_TTL_MS\n\n /**\n * Build full cache key with prefix.\n */\n function buildKey(key: string): string {\n return `${opts.prefix}:${key}`\n }\n\n /**\n * Check if entry is expired.\n */\n function isExpired(entry: TtlCacheEntry<any>): boolean {\n return Date.now() > entry.expiresAt\n }\n\n /**\n * Create a matcher function for a pattern (with wildcard support).\n * Returns a function that tests if a key matches the pattern.\n */\n function createMatcher(pattern: string): (key: string) => boolean {\n const fullPattern = buildKey(pattern)\n const hasWildcard = pattern.includes('*')\n\n if (!hasWildcard) {\n // Simple prefix matching (fast path).\n return (key: string) => key.startsWith(fullPattern)\n }\n\n // Wildcard matching with regex.\n const escaped = fullPattern.replaceAll(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const regexPattern = escaped.replaceAll('*', '.*')\n const regex = new RegExp(`^${regexPattern}`)\n return (key: string) => regex.test(key)\n }\n\n /**\n * Get cached data without fetching.\n *\n * @throws {TypeError} If key contains wildcards (*)\n */\n async function get<T>(key: string): Promise<T | undefined> {\n if (key.includes('*')) {\n throw new TypeError(\n 'Cache key cannot contain wildcards (*). Use getAll(pattern) to retrieve multiple entries.',\n )\n }\n\n const fullKey = buildKey(key)\n\n // Check in-memory cache first.\n if (opts.memoize) {\n const memoEntry = memoCache.get(fullKey)\n if (memoEntry && !isExpired(memoEntry)) {\n return memoEntry.data as T\n }\n // Remove expired memo entry.\n if (memoEntry) {\n memoCache.delete(fullKey)\n }\n }\n\n // Check persistent cache.\n const cacheEntry = await cacache.safeGet(fullKey)\n if (cacheEntry) {\n const entry = JSON.parse(\n cacheEntry.data.toString('utf8'),\n ) as TtlCacheEntry<T>\n if (!isExpired(entry)) {\n // Update in-memory cache.\n if (opts.memoize) {\n memoCache.set(fullKey, entry)\n }\n return entry.data\n }\n // Remove expired entry.\n await cacache.remove(fullKey)\n }\n\n return undefined\n }\n\n /**\n * Get all cached entries matching a pattern.\n * Supports wildcards (*) for flexible matching.\n */\n async function getAll<T>(pattern: string): Promise<Map<string, T>> {\n const results = new Map<string, T>()\n const matches = createMatcher(pattern)\n\n // Check in-memory cache first.\n if (opts.memoize) {\n for (const [key, entry] of memoCache.entries()) {\n if (!matches(key)) {\n continue\n }\n\n // Skip if expired.\n if (isExpired(entry)) {\n memoCache.delete(key)\n continue\n }\n\n // Add to results (strip cache prefix from key).\n const originalKey = key.slice((opts.prefix?.length ?? 0) + 1)\n results.set(originalKey, entry.data as T)\n }\n }\n\n // Check persistent cache for entries not in memory.\n const cacheDir = (await import('./paths')).getSocketCacacheDir()\n const cacacheModule = (await import('./cacache')) as any\n const stream = cacacheModule.getCacache().ls.stream(cacheDir)\n\n for await (const cacheEntry of stream) {\n // Skip if doesn't match our cache prefix.\n if (!cacheEntry.key.startsWith(`${opts.prefix}:`)) {\n continue\n }\n\n // Skip if doesn't match pattern.\n if (!matches(cacheEntry.key)) {\n continue\n }\n\n // Skip if already in results (from memory).\n const originalKey = cacheEntry.key.slice((opts.prefix?.length ?? 0) + 1)\n if (results.has(originalKey)) {\n continue\n }\n\n // Get entry from cache.\n try {\n const entry = await cacache.safeGet(cacheEntry.key)\n if (!entry) {\n continue\n }\n\n const parsed = JSON.parse(\n entry.data.toString('utf8'),\n ) as TtlCacheEntry<T>\n\n // Skip if expired.\n if (isExpired(parsed)) {\n await cacache.remove(cacheEntry.key)\n continue\n }\n\n // Add to results.\n results.set(originalKey, parsed.data)\n\n // Update in-memory cache.\n if (opts.memoize) {\n memoCache.set(cacheEntry.key, parsed)\n }\n } catch {\n // Ignore parse errors or other issues.\n }\n }\n\n return results\n }\n\n /**\n * Set cached data with TTL.\n *\n * @throws {TypeError} If key contains wildcards (*)\n */\n async function set<T>(key: string, data: T): Promise<void> {\n if (key.includes('*')) {\n throw new TypeError(\n 'Cache key cannot contain wildcards (*). Wildcards are only supported in clear({ prefix: \"pattern*\" }).',\n )\n }\n\n const fullKey = buildKey(key)\n const entry: TtlCacheEntry<T> = {\n data,\n expiresAt: Date.now() + ttl,\n }\n\n // Update in-memory cache.\n if (opts.memoize) {\n memoCache.set(fullKey, entry)\n }\n\n // Update persistent cache.\n await cacache.put(fullKey, JSON.stringify(entry), {\n metadata: { expiresAt: entry.expiresAt },\n })\n }\n\n /**\n * Get cached data or fetch and cache if missing/expired.\n */\n async function getOrFetch<T>(\n key: string,\n fetcher: () => Promise<T>,\n ): Promise<T> {\n const cached = await get<T>(key)\n if (cached !== undefined) {\n return cached\n }\n\n const data = await fetcher()\n await set(key, data)\n return data\n }\n\n /**\n * Delete a specific cache entry.\n *\n * @throws {TypeError} If key contains wildcards (*)\n */\n async function deleteEntry(key: string): Promise<void> {\n if (key.includes('*')) {\n throw new TypeError(\n 'Cache key cannot contain wildcards (*). Use deleteAll(pattern) to remove multiple entries.',\n )\n }\n\n const fullKey = buildKey(key)\n memoCache.delete(fullKey)\n await cacache.remove(fullKey)\n }\n\n /**\n * Delete all cache entries matching a pattern.\n * Supports wildcards (*) in patterns.\n * Delegates to cacache.clear() which handles pattern matching efficiently.\n */\n async function deleteAll(pattern?: string | undefined): Promise<number> {\n // Build full prefix/pattern by combining cache prefix with optional pattern.\n const fullPrefix = pattern ? `${opts.prefix}:${pattern}` : opts.prefix\n\n // Delete matching in-memory entries.\n if (!pattern) {\n // Delete all in-memory entries for this cache.\n memoCache.clear()\n } else {\n // Delete matching in-memory entries using shared matcher logic.\n const matches = createMatcher(pattern)\n for (const key of memoCache.keys()) {\n if (matches(key)) {\n memoCache.delete(key)\n }\n }\n }\n\n // Delete matching persistent cache entries.\n // Delegate to cacache.clear() which handles wildcards efficiently.\n const removed = await cacache.clear({ prefix: fullPrefix })\n return (removed ?? 0) as number\n }\n\n /**\n * Clear all cache entries (like Map.clear()).\n * Optionally clear only in-memory cache.\n */\n async function clear(options?: ClearOptions | undefined): Promise<void> {\n const opts = { __proto__: null, ...options } as ClearOptions\n\n // Clear in-memory cache.\n memoCache.clear()\n\n // If memoOnly, stop here.\n if (opts.memoOnly) {\n return\n }\n\n // Clear persistent cache.\n await deleteAll()\n }\n\n return {\n clear,\n delete: deleteEntry,\n deleteAll,\n get,\n getAll,\n getOrFetch,\n set,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @fileoverview Generic TTL-based caching utility using cacache.\n *\n * Provides a simple interface for caching data with time-to-live (TTL) expiration.\n * Uses cacache for persistent storage with metadata for TTL tracking.\n *\n * Features:\n * - Automatic expiration based on TTL\n * - In-memory memoization for hot data\n * - Persistent storage across process restarts\n * - Type-safe with generics\n *\n * Usage:\n * ```ts\n * const cache = createTtlCache({ ttl: 5 * 60 * 1000 }) // 5 minutes\n * const data = await cache.getOrFetch('key', async () => fetchData())\n * ```\n */\n\nimport * as cacache from './cacache'\n\nexport interface TtlCacheOptions {\n /**\n * Time-to-live in milliseconds.\n * @default 5 * 60 * 1000 (5 minutes)\n */\n ttl?: number | undefined\n /**\n * Enable in-memory memoization for hot data.\n * @default true\n */\n memoize?: boolean | undefined\n /**\n * Custom cache key prefix.\n * Must not contain wildcards (*).\n * Use clear({ prefix: \"pattern*\" }) for wildcard matching instead.\n *\n * @default 'ttl-cache'\n * @throws {TypeError} If prefix contains wildcards\n *\n * @example\n * // Valid\n * createTtlCache({ prefix: 'socket-sdk' })\n * createTtlCache({ prefix: 'my-app:cache' })\n *\n * @example\n * // Invalid - throws TypeError\n * createTtlCache({ prefix: 'socket-*' })\n */\n prefix?: string | undefined\n}\n\nexport interface TtlCacheEntry<T> {\n data: T\n expiresAt: number\n}\n\nexport interface ClearOptions {\n /**\n * Only clear in-memory memoization cache, not persistent cache.\n * Useful for forcing a refresh of cached data without removing it from disk.\n *\n * @default false\n */\n memoOnly?: boolean | undefined\n}\n\nexport interface TtlCache {\n /**\n * Get cached data without fetching.\n * Returns undefined if not found or expired.\n *\n * @param key - Cache key (must not contain wildcards)\n * @throws {TypeError} If key contains wildcards (*)\n */\n get<T>(key: string): Promise<T | undefined>\n /**\n * Get all cached entries matching a pattern.\n * Supports wildcards (*) for flexible matching.\n *\n * @param pattern - Key pattern (supports * wildcards, or use '*' for all entries)\n * @returns Map of matching entries (key -> value)\n *\n * @example\n * // Get all organization entries\n * const orgs = await cache.getAll<OrgData>('organizations:*')\n * for (const [key, org] of orgs) {\n * console.log(`${key}: ${org.name}`)\n * }\n *\n * @example\n * // Get all entries with this cache's prefix\n * const all = await cache.getAll<any>('*')\n */\n getAll<T>(pattern: string): Promise<Map<string, T>>\n /**\n * Get cached data or fetch and cache if missing/expired.\n *\n * @param key - Cache key (must not contain wildcards)\n */\n getOrFetch<T>(key: string, fetcher: () => Promise<T>): Promise<T>\n /**\n * Set cached data with TTL.\n *\n * @param key - Cache key (must not contain wildcards)\n * @throws {TypeError} If key contains wildcards (*)\n */\n set<T>(key: string, data: T): Promise<void>\n /**\n * Delete a specific cache entry.\n *\n * @param key - Cache key (must not contain wildcards)\n * @throws {TypeError} If key contains wildcards (*)\n */\n delete(key: string): Promise<void>\n /**\n * Delete all cache entries matching a pattern.\n * Supports wildcards (*) for flexible matching.\n *\n * @param pattern - Key pattern (supports * wildcards, or omit to delete all)\n * @returns Number of entries deleted\n *\n * @example\n * // Delete all entries with this cache's prefix\n * await cache.deleteAll()\n *\n * @example\n * // Delete entries matching prefix\n * await cache.deleteAll('organizations')\n *\n * @example\n * // Delete entries with wildcard pattern\n * await cache.deleteAll('scans:abc*')\n * await cache.deleteAll('npm/lodash/*')\n */\n deleteAll(pattern?: string | undefined): Promise<number>\n /**\n * Clear all cache entries (like Map.clear()).\n * Optionally clear only in-memory cache.\n *\n * @param options - Optional configuration\n * @param options.memoOnly - If true, only clears in-memory cache\n *\n * @example\n * // Clear everything (memory + disk)\n * await cache.clear()\n *\n * @example\n * // Clear only in-memory cache (force refresh)\n * await cache.clear({ memoOnly: true })\n */\n clear(options?: ClearOptions | undefined): Promise<void>\n}\n\n// 5 minutes\nconst DEFAULT_TTL_MS = 5 * 60 * 1000\nconst DEFAULT_PREFIX = 'ttl-cache'\n\n/**\n * Create a TTL-based cache instance.\n */\nexport function createTtlCache(options?: TtlCacheOptions): TtlCache {\n const opts = {\n __proto__: null,\n memoize: true,\n prefix: DEFAULT_PREFIX,\n ttl: DEFAULT_TTL_MS,\n ...options,\n } as Required<TtlCacheOptions>\n\n // Validate prefix does not contain wildcards.\n if (opts.prefix?.includes('*')) {\n throw new TypeError(\n 'Cache prefix cannot contain wildcards (*). Use clear({ prefix: \"pattern*\" }) for wildcard matching.',\n )\n }\n\n // In-memory cache for hot data\n const memoCache = new Map<string, TtlCacheEntry<any>>()\n\n // Ensure ttl is defined\n const ttl = opts.ttl ?? DEFAULT_TTL_MS\n\n /**\n * Build full cache key with prefix.\n */\n function buildKey(key: string): string {\n return `${opts.prefix}:${key}`\n }\n\n /**\n * Check if entry is expired.\n */\n function isExpired(entry: TtlCacheEntry<any>): boolean {\n return Date.now() > entry.expiresAt\n }\n\n /**\n * Create a matcher function for a pattern (with wildcard support).\n * Returns a function that tests if a key matches the pattern.\n */\n function createMatcher(pattern: string): (key: string) => boolean {\n const fullPattern = buildKey(pattern)\n const hasWildcard = pattern.includes('*')\n\n if (!hasWildcard) {\n // Simple prefix matching (fast path).\n return (key: string) => key.startsWith(fullPattern)\n }\n\n // Wildcard matching with regex.\n const escaped = fullPattern.replaceAll(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const regexPattern = escaped.replaceAll('*', '.*')\n const regex = new RegExp(`^${regexPattern}`)\n return (key: string) => regex.test(key)\n }\n\n /**\n * Get cached data without fetching.\n *\n * @throws {TypeError} If key contains wildcards (*)\n */\n async function get<T>(key: string): Promise<T | undefined> {\n if (key.includes('*')) {\n throw new TypeError(\n 'Cache key cannot contain wildcards (*). Use getAll(pattern) to retrieve multiple entries.',\n )\n }\n\n const fullKey = buildKey(key)\n\n // Check in-memory cache first.\n if (opts.memoize) {\n const memoEntry = memoCache.get(fullKey)\n if (memoEntry && !isExpired(memoEntry)) {\n return memoEntry.data as T\n }\n // Remove expired memo entry.\n if (memoEntry) {\n memoCache.delete(fullKey)\n }\n }\n\n // Check persistent cache.\n const cacheEntry = await cacache.safeGet(fullKey)\n if (cacheEntry) {\n const entry = JSON.parse(\n cacheEntry.data.toString('utf8'),\n ) as TtlCacheEntry<T>\n if (!isExpired(entry)) {\n // Update in-memory cache.\n if (opts.memoize) {\n memoCache.set(fullKey, entry)\n }\n return entry.data\n }\n // Remove expired entry.\n await cacache.remove(fullKey)\n }\n\n return undefined\n }\n\n /**\n * Get all cached entries matching a pattern.\n * Supports wildcards (*) for flexible matching.\n */\n async function getAll<T>(pattern: string): Promise<Map<string, T>> {\n const results = new Map<string, T>()\n const matches = createMatcher(pattern)\n\n // Check in-memory cache first.\n if (opts.memoize) {\n for (const [key, entry] of memoCache.entries()) {\n if (!matches(key)) {\n continue\n }\n\n // Skip if expired.\n if (isExpired(entry)) {\n memoCache.delete(key)\n continue\n }\n\n // Add to results (strip cache prefix from key).\n const originalKey = key.slice((opts.prefix?.length ?? 0) + 1)\n results.set(originalKey, entry.data as T)\n }\n }\n\n // Check persistent cache for entries not in memory.\n const cacheDir = (await import('./paths')).getSocketCacacheDir()\n const cacacheModule = (await import('./cacache')) as any\n const stream = cacacheModule.getCacache().ls.stream(cacheDir)\n\n for await (const cacheEntry of stream) {\n // Skip if doesn't match our cache prefix.\n if (!cacheEntry.key.startsWith(`${opts.prefix}:`)) {\n continue\n }\n\n // Skip if doesn't match pattern.\n if (!matches(cacheEntry.key)) {\n continue\n }\n\n // Skip if already in results (from memory).\n const originalKey = cacheEntry.key.slice((opts.prefix?.length ?? 0) + 1)\n if (results.has(originalKey)) {\n continue\n }\n\n // Get entry from cache.\n try {\n const entry = await cacache.safeGet(cacheEntry.key)\n if (!entry) {\n continue\n }\n\n const parsed = JSON.parse(\n entry.data.toString('utf8'),\n ) as TtlCacheEntry<T>\n\n // Skip if expired.\n if (isExpired(parsed)) {\n await cacache.remove(cacheEntry.key)\n continue\n }\n\n // Add to results.\n results.set(originalKey, parsed.data)\n\n // Update in-memory cache.\n if (opts.memoize) {\n memoCache.set(cacheEntry.key, parsed)\n }\n } catch {\n // Ignore parse errors or other issues.\n }\n }\n\n return results\n }\n\n /**\n * Set cached data with TTL.\n *\n * @throws {TypeError} If key contains wildcards (*)\n */\n async function set<T>(key: string, data: T): Promise<void> {\n if (key.includes('*')) {\n throw new TypeError(\n 'Cache key cannot contain wildcards (*). Wildcards are only supported in clear({ prefix: \"pattern*\" }).',\n )\n }\n\n const fullKey = buildKey(key)\n const entry: TtlCacheEntry<T> = {\n data,\n expiresAt: Date.now() + ttl,\n }\n\n // Update in-memory cache first (synchronous and fast).\n if (opts.memoize) {\n memoCache.set(fullKey, entry)\n }\n\n // Update persistent cache (don't fail if this errors).\n // In-memory cache is already updated, so immediate reads will succeed.\n try {\n await cacache.put(fullKey, JSON.stringify(entry), {\n metadata: { expiresAt: entry.expiresAt },\n })\n } catch {\n // Ignore persistent cache errors - in-memory cache is the source of truth.\n // This can happen during test setup or if the cache directory is not accessible.\n }\n }\n\n /**\n * Get cached data or fetch and cache if missing/expired.\n */\n async function getOrFetch<T>(\n key: string,\n fetcher: () => Promise<T>,\n ): Promise<T> {\n const cached = await get<T>(key)\n if (cached !== undefined) {\n return cached\n }\n\n const data = await fetcher()\n await set(key, data)\n return data\n }\n\n /**\n * Delete a specific cache entry.\n *\n * @throws {TypeError} If key contains wildcards (*)\n */\n async function deleteEntry(key: string): Promise<void> {\n if (key.includes('*')) {\n throw new TypeError(\n 'Cache key cannot contain wildcards (*). Use deleteAll(pattern) to remove multiple entries.',\n )\n }\n\n const fullKey = buildKey(key)\n memoCache.delete(fullKey)\n await cacache.remove(fullKey)\n }\n\n /**\n * Delete all cache entries matching a pattern.\n * Supports wildcards (*) in patterns.\n * Delegates to cacache.clear() which handles pattern matching efficiently.\n */\n async function deleteAll(pattern?: string | undefined): Promise<number> {\n // Build full prefix/pattern by combining cache prefix with optional pattern.\n const fullPrefix = pattern ? `${opts.prefix}:${pattern}` : opts.prefix\n\n // Delete matching in-memory entries.\n if (!pattern) {\n // Delete all in-memory entries for this cache.\n memoCache.clear()\n } else {\n // Delete matching in-memory entries using shared matcher logic.\n const matches = createMatcher(pattern)\n for (const key of memoCache.keys()) {\n if (matches(key)) {\n memoCache.delete(key)\n }\n }\n }\n\n // Delete matching persistent cache entries.\n // Delegate to cacache.clear() which handles wildcards efficiently.\n const removed = await cacache.clear({ prefix: fullPrefix })\n return (removed ?? 0) as number\n }\n\n /**\n * Clear all cache entries (like Map.clear()).\n * Optionally clear only in-memory cache.\n */\n async function clear(options?: ClearOptions | undefined): Promise<void> {\n const opts = { __proto__: null, ...options } as ClearOptions\n\n // Clear in-memory cache.\n memoCache.clear()\n\n // If memoOnly, stop here.\n if (opts.memoOnly) {\n return\n }\n\n // Clear persistent cache.\n await deleteAll()\n }\n\n return {\n clear,\n delete: deleteEntry,\n deleteAll,\n get,\n getAll,\n getOrFetch,\n set,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["options", "opts"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "license": "MIT",
5
5
  "description": "Core utilities and infrastructure for Socket.dev security tools",
6
6
  "keywords": [