d7fb7b7a7b
Build & Deploy / Build & Push Docker Image (push) Failing after 47s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / API Unit Tests (push) Failing after 5m4s
Test / Marketplace Unit Tests (push) Failing after 4m55s
Test / Admin Unit Tests (push) Successful in 9m37s
Test / Dashboard Unit Tests (push) Successful in 9m37s
Test / API Integration Tests (push) Successful in 9m54s
47 lines
1.2 KiB
TypeScript
47 lines
1.2 KiB
TypeScript
import type { DemoSubmissionResult } from './schema';
|
|
|
|
interface StoredOperation {
|
|
expiresAt: number;
|
|
result: Promise<DemoSubmissionResult>;
|
|
}
|
|
|
|
const globalStore = globalThis as typeof globalThis & {
|
|
__rdgDemoIdempotencyStore?: Map<string, StoredOperation>;
|
|
};
|
|
|
|
const store = globalStore.__rdgDemoIdempotencyStore ?? new Map<string, StoredOperation>();
|
|
globalStore.__rdgDemoIdempotencyStore = store;
|
|
|
|
function purgeExpired(now: number) {
|
|
for (const [key, operation] of store) {
|
|
if (operation.expiresAt <= now) store.delete(key);
|
|
}
|
|
}
|
|
|
|
export async function executeIdempotent(
|
|
key: string,
|
|
ttlSeconds: number,
|
|
operation: () => Promise<DemoSubmissionResult>,
|
|
): Promise<DemoSubmissionResult> {
|
|
const now = Date.now();
|
|
purgeExpired(now);
|
|
const existing = store.get(key);
|
|
if (existing) return existing.result;
|
|
|
|
const result = operation();
|
|
store.set(key, { expiresAt: now + ttlSeconds * 1_000, result });
|
|
|
|
try {
|
|
const settled = await result;
|
|
if (!settled.ok && settled.retryable) store.delete(key);
|
|
return settled;
|
|
} catch (error) {
|
|
store.delete(key);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function clearIdempotencyStoreForTests() {
|
|
store.clear();
|
|
}
|