import { KvEntry } from 'nats'; import NatsClient from './NatsClient'; const BUCKET = 'card_state'; /** 1 hour TTL in milliseconds — sliding window, reset on each access. */ const TTL_MS = 60 * 60 * 1000; /** * Cached card data stored in NATS KV. * Keyed by the stripped card id (rawBarcode % 200000000000). * TTL of 1 hour of inactivity — re-put on each access to slide the window. */ export interface CardKVEntry { runnerId: number; runnerDisplayName: string; enabled: boolean; } async function getBucket() { return NatsClient.getKV(BUCKET, { ttl: TTL_MS }); } function entryKey(cardId: number): string { return `card.${cardId}`; } /** * Returns the cached CardKVEntry for the given stripped card id, or null on a miss. * On a cache hit the entry is re-put with a fresh TTL to slide the inactivity window. */ export async function getCardEntry(cardId: number): Promise { const bucket = await getBucket(); let entry: KvEntry | null = null; try { entry = await bucket.get(entryKey(cardId)); } catch { return null; } if (!entry || entry.operation === 'DEL' || entry.operation === 'PURGE') { return null; } const value = JSON.parse(entry.string()) as CardKVEntry; // Re-put to slide the TTL window await bucket.put(entryKey(cardId), JSON.stringify(value)); return value; } /** * Writes a CardKVEntry for the given stripped card id with a 1-hour TTL. */ export async function setCardEntry(cardId: number, entry: CardKVEntry): Promise { const bucket = await getBucket(); await bucket.put(entryKey(cardId), JSON.stringify(entry)); } /** * Removes the cached entry for the given stripped card id. * Call on card update (runner reassignment, enable/disable change) or delete. */ export async function deleteCardEntry(cardId: number): Promise { const bucket = await getBucket(); try { await bucket.delete(entryKey(cardId)); } catch { // Entry may not exist in KV yet — that's fine } }