MCPcopy
hub / github.com/claude-code-best/claude-code / memoizeWithTTLAsync

Function memoizeWithTTLAsync

src/utils/memoize.ts:120–220  ·  view source on GitHub ↗
(
  f: (...args: Args) => Promise<Result>,
  cacheLifetimeMs: number = 5 * 60 * 1000, // Default 5 minutes
)

Source from the content-addressed store, hash-verified

118 * @returns A memoized version of the async function
119 */
120export function memoizeWithTTLAsync<Args extends unknown[], Result>(
121 f: (...args: Args) => Promise<Result>,
122 cacheLifetimeMs: number = 5 * 60 * 1000, // Default 5 minutes
123): ((...args: Args) => Promise<Result>) & { cache: { clear: () => void } } {
124 const cache = new Map<string, CacheEntry<Result>>()
125 // In-flight cold-miss dedup. The old memoizeWithTTL (sync) accidentally
126 // provided this: it stored the Promise synchronously before the first
127 // await, so concurrent callers shared one f() invocation. This async
128 // variant awaits before cache.set, so concurrent cold-miss callers would
129 // each invoke f() independently without this map. For
130 // refreshAndGetAwsCredentials that means N concurrent `aws sso login`
131 // spawns. Same pattern as pending401Handlers in auth.ts:1171.
132 const inFlight = new Map<string, Promise<Result>>()
133
134 const memoized = async (...args: Args): Promise<Result> => {
135 const key = jsonStringify(args)
136 const cached = cache.get(key)
137 const now = Date.now()
138
139 // Populate cache - if this throws, nothing gets cached
140 if (!cached) {
141 const pending = inFlight.get(key)
142 if (pending) return pending
143 const promise = f(...args)
144 inFlight.set(key, promise)
145 try {
146 const result = await promise
147 // Identity-guard: cache.clear() during the await should discard this
148 // result (clear intent is to invalidate). If we're still in-flight,
149 // store it. clear() wipes inFlight too, so this check catches that.
150 if (inFlight.get(key) === promise) {
151 cache.set(key, {
152 value: result,
153 timestamp: now,
154 refreshing: false,
155 })
156 }
157 return result
158 } finally {
159 if (inFlight.get(key) === promise) {
160 inFlight.delete(key)
161 }
162 }
163 }
164
165 // If we have a stale cache entry and it's not already refreshing
166 if (
167 cached &&
168 now - cached.timestamp > cacheLifetimeMs &&
169 !cached.refreshing
170 ) {
171 // Mark as refreshing to prevent multiple parallel refreshes
172 cached.refreshing = true
173
174 // Schedule async refresh (non-blocking). Both .then and .catch are
175 // identity-guarded against a concurrent cache.clear() + cold-miss
176 // storing a newer entry while this refresh is in flight. .then
177 // overwriting with the stale refresh's result is worse than .catch

Callers 3

auth.tsFile · 0.85
memoize.test.tsFile · 0.85
metricsOptOut.tsFile · 0.85

Calls 1

clearMethod · 0.45

Tested by

no test coverage detected