| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const DEFAULT_WINDOW_MS = 5000; |
|
|
| |
| const idempotencyStore = new Map(); |
|
|
| |
| let cleanupInterval; |
|
|
| function ensureCleanup() { |
| if (cleanupInterval) return; |
| cleanupInterval = setInterval(() => { |
| const now = Date.now(); |
| for (const [key, entry] of idempotencyStore) { |
| if (now >= entry.expiresAt) { |
| idempotencyStore.delete(key); |
| } |
| } |
| }, 30000); |
| |
| if (cleanupInterval.unref) cleanupInterval.unref(); |
| } |
|
|
| |
| |
| |
| |
| |
| export function getIdempotencyKey(headers) { |
| if (!headers) return null; |
| const get = typeof headers.get === "function" ? (k) => headers.get(k) : (k) => headers[k]; |
| return get("idempotency-key") || get("x-request-id") || null; |
| } |
|
|
| |
| |
| |
| |
| |
| export function checkIdempotency(key) { |
| if (!key) return null; |
| const entry = idempotencyStore.get(key); |
| if (!entry) return null; |
| if (Date.now() >= entry.expiresAt) { |
| idempotencyStore.delete(key); |
| return null; |
| } |
| return { response: entry.response, status: entry.status }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function saveIdempotency(key, response, status, windowMs = DEFAULT_WINDOW_MS) { |
| if (!key) return; |
| ensureCleanup(); |
| idempotencyStore.set(key, { |
| response, |
| status, |
| expiresAt: Date.now() + windowMs, |
| }); |
| } |
|
|
| |
| |
| |
| export function getIdempotencyStats() { |
| return { |
| activeKeys: idempotencyStore.size, |
| windowMs: DEFAULT_WINDOW_MS, |
| }; |
| } |
|
|
| |
| |
| |
| export function clearIdempotency() { |
| idempotencyStore.clear(); |
| } |
|
|