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": "function createIdempotencyGuard() {\n const inFlight = new Map<string, Promise>();\n const cache = new Map<string, { value: T; expiry: number }>();\n\n return async function guard({ key, ttlMs, handler }: { key: string; ttlMs: number; handler: () => Promise }): Promise {\n // Return in-flight promise if handler is already running for this key\n const existing = inFlight.get(key);\n if (existing) return existing;\n\n // Return cached value if still valid\n const cached = cache.get(key);\n if (cached && Date.now() < cached.expiry) return cached.value;\n\n // Create and store new promise\n const promise = handler()\n .then((result) => {\n cache.set(key, { value: result, expiry: Date.now() + ttlMs });\n return result;\n })\n .catch((err) => {\n inFlight.delete(key); // Allow retry on failure\n throw err;\n })\n .finally(() => {\n inFlight.delete(key);\n });\n\n inFlight.set(key, promise);\n return promise;\n };\n}"
,
"explanation": "The function returns a guard that manages in-flight promises and a TTL cache per key. On first call for a key, it runs the handler and stores the promise in inFlight. Concurrent calls with the same key receive the same promise. On success, the result is cached with an expiry timestamp. On failure, the key is removed from inFlight (but not cached), allowing future calls to retry. The finally block ensures cleanup. All operations use native Map/Date primitives.",
"complexity": {
"time": "O(1) average for all operations (Map get/set/delete, Date.now)",
"space": "O(K) where K is the number of unique keys with active in-flight requests or valid cached entries"
},
"tests": [
"const guard = createIdempotencyGuard(); let runs = 0; const p1 = guard({ key: 'a', ttlMs: 1000, handler: async () => { runs++; return 1; } }); const p2 = guard({ key: 'a', ttlMs: 1000, handler: async () => { runs++; return 2; } }); await Promise.all([p1, p2]); console.assert(runs === 1, 'concurrency: handler runs once'); console.assert(p1 === p2, 'concurrency: same promise returned');",
"const guard = createIdempotencyGuard(); let runs = 0; await guard({ key: 'b', ttlMs: 1000, handler: async () => { runs++; return 'x'; } }); await guard({ key: 'b', ttlMs: 1000, handler: async () => { runs++; return 'y'; } }); console.assert(runs === 1, 'caching: handler not re-run within TTL');",
"const guard = createIdempotencyGuard(); let runs = 0; await guard({ key: 'c', ttlMs: 50, handler: async () => { runs++; return runs; } }); await new Promise(r => setTimeout(r, 60)); await guard({ key: 'c', ttlMs: 50, handler: async () => { runs++; return runs; } }); console.assert(runs === 2, 'expiry: handler re-run after TTL expires');",
"const guard = createIdempotencyGuard(); let runs = 0; try { await guard({ key: 'd', ttlMs: 1000, handler: async () => { runs++; throw new Error('fail'); } }); } catch {} try { await guard({ key: 'd', ttlMs: 1000, handler: async () => { runs++; return 'ok'; } }); } catch {} console.assert(runs === 2, 'errors: failure not cached, retry executes handler again');",
"const guard = createIdempotencyGuard(); let runs = 0; await guard({ key: 'e1', ttlMs: 1000, handler: async () => { runs++; return 1; } }); await guard({ key: 'e2', ttlMs: 1000, handler: async () => { runs++; return 2; } }); console.assert(runs === 2, 'independence: different keys tracked separately');"
]
}
8/8 checks passed