Loading cell…
Loading cell…
Write a TypeScript function named createIdempotencyGuard.
Requirements:
key, ttlMs, and an async handler function.Output format: { "code": "...", "explanation": "...", "complexity": { "time": "...", "space": "..." }, "tests": ["...", "...", "...", "...", "..."] }
{
"code": "type IdempotencyOptions<K extends PropertyKey, T> = {\n key: K;\n ttlMs: number;\n handler: () => Promise;\n};\n\ntype CacheEntry = {\n promise: Promise;\n value?: unknown;\n expiresAt?: number;\n};\n\nconst idempotencyCache = new Map<PropertyKey, CacheEntry>();\n\nfunction createIdempotencyGuard<K extends PropertyKey, T>({\n key,\n ttlMs,\n handler,\n}: IdempotencyOptions<K, T>): Promise {\n const now = Date.now();\n const existing = idempotencyCache.get(key);\n\n if (existing) {\n if (existing.expiresAt !== undefined) {\n if (now < existing.expiresAt) {\n return Promise.resolve(existing.value as T);\n }\n\n idempotencyCache.delete(key);\n } else {\n return existing.promise as Promise;\n }\n }\n\n let entry: CacheEntry;\n const rawPromise = Promise.resolve().then(handler);\n\n const trackedPromise = rawPromise.then(\n (value) => {\n if (idempotencyCache.get(key) === entry) {\n entry.value = value;\n entry.expiresAt = Date.now() + ttlMs;\n }\n return value;\n },\n (error) => {\n if (idempotencyCache.get(key) === entry) {\n idempotencyCache.delete(key);\n }\n throw error;\n },\n );\n\n entry = { promise: trackedPromise };\n idempotencyCache.set(key, entry);\n\n return trackedPromise;\n}",
"explanation": "The cache maps each key to either an in-flight Promise or a successfully completed value with an expiration timestamp. A second call made while a handler is running returns the exact same in-flight Promise. Successful results are cached until ttlMs has elapsed. Rejected handlers remove their entry, allowing later calls to retry. Expired entries are removed lazily when their key is accessed.",
"complexity": {
"time": "Expected O(1) per call, excluding the time spent executing the handler.",
"space": "O(n), where n is the number of distinct keys retained in the in-memory map. Expired entries are removed lazily when accessed."
},
"tests": [
"const key = concurrency-${Date.now()}; let calls = 0; let release!: () => void; const blocker = new Promise((resolve) => { release = resolve; }); const handler = async () => { calls++; await blocker; return 42; }; const p1 = createIdempotencyGuard({ key, ttlMs: 1000, handler }); const p2 = createIdempotencyGuard({ key, ttlMs: 1000, handler }); console.assert(p1 === p2); release(); console.assert((await p1) === 42); console.assert(calls === 1);",
"const key = cache-${Date.now()}; let calls = 0; const handler = async () => ++calls; const first = await createIdempotencyGuard({ key, ttlMs: 1000, handler }); const second = await createIdempotencyGuard({ key, ttlMs: 1000, handler }); console.assert(first === 1); console.assert(second === 1); console.assert(calls === 1);",
"const key = expiry-${Date.now()}; let calls = 0; const handler = async () => ++calls; const first = await createIdempotencyGuard({ key, ttlMs: 20, handler }); await new Promise((resolve) => setTimeout(resolve, 30)); const second = await createIdempotencyGuard({ key, ttlMs: 20, handler }); console.assert(first === 1); console.assert(second === 2); console.assert(calls === 2);",
"const key = error-${Date.now()}; let calls = 0; const handler = async () => { calls++; if (calls === 1) throw new Error('expected'); return 'ok'; }; await createIdempotencyGuard({ key, ttlMs: 1000, handler }).catch((error: Error) => console.assert(error.message === 'expected')); const result = await createIdempotencyGuard({ key, ttlMs: 1000, handler }); console.assert(result === 'ok'); console.assert(calls === 2);",
"const key = error-concurrency-${Date.now()}; let calls = 0; const handler = async () => { calls++; throw new Error('same failure'); }; const p1 = createIdempotencyGuard({ key, ttlMs: 1000, handler }); const p2 = createIdempotencyGuard({ key, ttlMs: 1000, handler }); console.assert(p1 === p2); const errors = await Promise.all([p1.catch((e) => e), p2.catch((e) => e)]); console.assert(errors[0] === errors[1]); console.assert(calls === 1); const retry = createIdempotencyGuard({ key, ttlMs: 1000, handler }); await retry.catch(() => undefined); console.assert(calls === 2);",
"const key = different-keys-${Date.now()}; let calls = 0; const handler = async () => ++calls; const first = await createIdempotencyGuard({ key: ${key}-a, ttlMs: 1000, handler }); const second = await createIdempotencyGuard({ key: ${key}-b, ttlMs: 1000, handler }); console.assert(first === 1); console.assert(second === 2); console.assert(calls === 2);"
]
}
8/8 checks passed