UNPKG

accounts

Version:

Tempo Accounts SDK

76 lines 2.52 kB
/** * Per-`kv`-instance in-memory L1 cache. Lives for the lifetime of the worker * (or process) and short-circuits the L2 `kv.get` on hot keys so a request * with N callers that share the same `cached()` key only pays for one * remote read. * * `WeakMap` keys mean dropping a `kv` reference releases its L1 entries * automatically — no need to thread a lifecycle hook through callers. */ const memoryStore = new WeakMap(); /** * Per-`kv`-instance in-flight dedupe table. Multiple concurrent * `cached(kv, key, fn)` calls for the same key share a single `fn()` * invocation instead of stampeding the upstream. */ const inflightStore = new WeakMap(); /** * Reads `key` from a two-tier cache (in-memory L1, `kv`-backed L2). If absent * or expired in both tiers, calls `fn`, stores the result with a * `Date.now() + ttl * 1000` expiry in both tiers, and returns it. Defaults * to caching forever (no expiry). Kv read/write failures are swallowed so * cache misses never break the caller. * * Concurrent calls for the same key share a single `fn()` invocation. */ export async function cached(kv, key, fn, options = {}) { const { ttl = Infinity } = options; // L1: in-memory. const memory = memoryFor(kv); const local = memory.get(key); if (local && local.expiresAt > Date.now()) return local.value; // Coalesce concurrent calls for the same key. const inflight = inflightFor(kv); const pending = inflight.get(key); if (pending) return pending; const promise = (async () => { // L2: kv. const entry = await kv.get(key).catch(() => null); if (entry && entry.expiresAt > Date.now()) { memory.set(key, entry); return entry.value; } const value = await fn(); const expiresAt = ttl === Infinity ? Infinity : Date.now() + ttl * 1000; const fresh = { expiresAt, value }; memory.set(key, fresh); await kv.set(key, fresh).catch(() => { }); return value; })(); inflight.set(key, promise); try { return await promise; } finally { inflight.delete(key); } } function memoryFor(kv) { let map = memoryStore.get(kv); if (!map) { map = new Map(); memoryStore.set(kv, map); } return map; } function inflightFor(kv) { let map = inflightStore.get(kv); if (!map) { map = new Map(); inflightStore.set(kv, map); } return map; } //# sourceMappingURL=kv.js.map