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": "export interface IdempotencyOptions {\n key: string;\n ttlMs: number;\n handler: () => Promise;\n}\n\ninterface CacheEntry {\n value: T;\n expiresAt: number;\n}\n\nexport function createIdempotencyGuard() {\n const inFlight = new Map<string, Promise>();\n const cache = new Map<string, CacheEntry>();\n\n return async function idempotent({ key, ttlMs, handler }: IdempotencyOptions): Promise {\n const now = Date.now();\n\n // Check cache first\n const cached = cache.get(key);\n if (cached && cached.expiresAt > now) {\n return cached.value as T;\n }\n\n // If expired or not cached, remove stale entry\n if (cached) {\n cache.delete(key);\n }\n\n // Check for in-flight request\n const existing = inFlight.get(key);\n if (existing) {\n return existing as Promise;\n }\n\n // Start new request\n const promise = handler()\n .then((result) => {\n cache.set(key, { value: result, expiresAt: Date.now() + ttlMs });\n return result;\n })\n .catch((error) => {\n // Do not cache failures\n throw error;\n })\n .finally(() => {\n inFlight.delete(key);\n });\n\n inFlight.set(key, promise);\n return promise as Promise;\n };\n}\n\n// Type-safe wrapper for convenience\nexport type IdempotentFunction = (options: IdempotencyOptions) => Promise;",
"explanation": "The createIdempotencyGuard factory returns an async function that enforces idempotency per key. It maintains two Maps: inFlight tracks currently executing promises so concurrent calls with the same key receive the same promise, and cache stores successful results with expiration timestamps. On each call, it first checks for a valid cached result (O(1)). If none, it checks for an in-flight request and returns that promise if found. Otherwise, it invokes the handler, caches the result on success with a TTL-based expiry, and ensures the in-flight entry is cleaned up in finally. Errors are not cached, allowing retries. All operations use native Map/Date primitives.",
"complexity": {
"time": "O(1) average case for all operations (Map get/set/delete, Date.now). Handler execution time is excluded.",
"space": "O(K) where K is the number of unique keys with either in-flight requests or unexpired cached entries. Each entry stores a promise reference or a value + timestamp."
},
"tests": [
"async function testConcurrency() { const guard = createIdempotencyGuard(); let calls = 0; const handler = async () => { calls++; await new Promise(r => setTimeout(r, 10)); return 'ok'; }; const [r1, r2, r3] = await Promise.all([guard({key:'a', ttlMs:1000, handler}), guard({key:'a', ttlMs:1000, handler}), guard({key:'a', ttlMs:1000, handler})]); console.assert(calls === 1, 'handler called once'); console.assert(r1 === r2 && r2 === r3 && r1 === 'ok', 'all return same result'); }",
"async function testCaching() { const guard = createIdempotencyGuard(); let calls = 0; const handler = async () => { calls++; return 'cached'; }; await guard({key:'b', ttlMs:1000, handler}); await guard({key:'b', ttlMs:1000, handler}); console.assert(calls === 1, 'handler called once due to cache'); }",
"async function testExpiry() { const guard = createIdempotencyGuard(); let calls = 0; const handler = async () => { calls++; return 'fresh'; }; await guard({key:'c', ttlMs:50, handler}); await new Promise(r => setTimeout(r, 60)); await guard({key:'c', ttlMs:50, handler}); console.assert(calls === 2, 'handler called again after TTL expiry'); }",
"async function testErrorNotCached() { const guard = createIdempotencyGuard(); let calls = 0; const handler = async () => { calls++; throw new Error('fail'); }; try { await guard({key:'d', ttlMs:1000, handler}); } catch {} try { await guard({key:'d', ttlMs:1000, handler}); } catch {} console.assert(calls === 2, 'handler retried after error, not cached'); }",
"async function testDifferentKeysIndependent() { const guard = createIdempotencyGuard(); let calls = 0; const handler = async (v: string) => { calls++; return v; }; const [r1, r2] = await Promise.all([guard({key:'x', ttlMs:1000, handler: () => handler('x')}), guard({key:'y', ttlMs:1000, handler: () => handler('y')})]); console.assert(calls === 2, 'different keys invoke handler independently'); console.assert(r1 === 'x' && r2 === 'y', 'correct results per key'); }"
]
}
8/8 checks passed