/** Successful send fingerprints are reused for this long (7 days). */ export const IDEMPOTENCY_RETENTION_MS = 7 * 24 * 60 * 60 * 1000 /** Concurrent workers with the same key share this short inflight window. */ export const IDEMPOTENCY_INFLIGHT_MS = 2 * 60 * 1000 const MAX_KEY_LENGTH = 256 export type IdempotencyFingerprint = | { status: 'success' result: unknown completedAt: string } | { status: 'inflight' startedAt: string } export type IdempotencyStorage = { get: (key: string) => Promise set: (key: string, value: unknown) => Promise delete: (key: string) => Promise } export function normalizeIdempotencyKey(raw: string | undefined | null): string | null { if (typeof raw !== 'string') return null const key = raw.trim() if (!key) return null if (key.length > MAX_KEY_LENGTH) { throw new Error(`idempotencyKey must be at most ${MAX_KEY_LENGTH} characters.`) } return key } export function idempotencyStorageKey(action: string, account: string, key: string): string { return `idempotency:${action}:${account}:${key}` } export function readFreshFingerprint( value: unknown, nowMs: number = Date.now(), ): { kind: 'success'; result: unknown } | { kind: 'inflight' } | null { if (!value || typeof value !== 'object' || Array.isArray(value)) return null const record = value as IdempotencyFingerprint if (record.status === 'success' && typeof record.completedAt === 'string') { const completed = Date.parse(record.completedAt) if (!Number.isNaN(completed) && nowMs - completed < IDEMPOTENCY_RETENTION_MS) { return { kind: 'success', result: record.result } } return null } if (record.status === 'inflight' && typeof record.startedAt === 'string') { const started = Date.parse(record.startedAt) if (!Number.isNaN(started) && nowMs - started < IDEMPOTENCY_INFLIGHT_MS) { return { kind: 'inflight' } } return null } return null } /** * Run a side-effecting send at most once per `(account, idempotencyKey)` within * the retention window. Omitting the key leaves behavior unchanged. */ export async function withSendIdempotency(options: { action: string account: string idempotencyKey?: string run: () => Promise storage?: IdempotencyStorage }): Promise { const key = normalizeIdempotencyKey(options.idempotencyKey) if (!key) return await options.run() const storage = options.storage ?? (await import('kody:runtime')).packageStorage() const storageKey = idempotencyStorageKey(options.action, options.account, key) const existing = readFreshFingerprint(await storage.get(storageKey)) if (existing?.kind === 'success') return existing.result as T if (existing?.kind === 'inflight') { throw new Error( `Send already in progress for idempotencyKey "${key}"; retry shortly to receive the prior successful result.`, ) } await storage.set(storageKey, { status: 'inflight', startedAt: new Date().toISOString(), } satisfies IdempotencyFingerprint) try { const result = await options.run() await storage.set(storageKey, { status: 'success', result, completedAt: new Date().toISOString(), } satisfies IdempotencyFingerprint) return result } catch (error) { await storage.delete(storageKey) throw error } }