{"version":3,"file":"sieve-cache.cjs","names":[],"sources":["../../src/sieve-cache.ts"],"sourcesContent":["export type SieveCache<TKey, TValue> = {\n  get: (key: TKey) => TValue | undefined\n  set: (key: TKey, value: TValue) => void\n  clear: () => void\n}\n\n/**\n * A fixed-capacity cache using the SIEVE eviction algorithm\n * (https://cachemon.github.io/SIEVE-website/).\n *\n * Entries live in the Map's FIFO insertion order; a hit only flips a `visited`\n * bit instead of relinking the entry, which makes `get` (by far the hottest\n * operation here) one `Map.get` plus a boolean store. Eviction sweeps a `hand`\n * from the oldest entry towards the newest, clearing `visited` bits until it\n * finds an unvisited entry to drop, so entries touched since the last sweep\n * survive one more round. This keeps LRU-like hit ratios while being\n * scan-resistant.\n */\nexport function createSieveCache<TKey, TValue>(\n  max: number,\n): SieveCache<TKey, TValue> {\n  type Node = {\n    key: TKey\n    value: TValue\n    visited: boolean\n  }\n  const cache = new Map<TKey, Node>()\n  let hand: IterableIterator<Node> | undefined\n  let newest: Node | undefined\n\n  return {\n    get(key) {\n      const entry = cache.get(key)\n      if (!entry) {\n        return undefined\n      }\n      entry.visited = true\n      return entry.value\n    },\n    set(key, value) {\n      const existing = cache.get(key)\n      if (existing) {\n        existing.value = value\n        return\n      }\n      if (cache.size >= max) {\n        // sweep from `hand` towards `newest` (wrapping around to `oldest`),\n        // clearing `visited` bits, and evict the first unvisited entry\n        let node = hand?.next().value\n        while (!node || node.visited) {\n          if (node) {\n            node.visited = false\n          } else {\n            hand = cache.values()\n          }\n          node = hand!.next().value\n        }\n        // Live Map iterators see later insertions. Reset at the boundary so the\n        // next sweep wraps to the oldest entry instead of visiting a replacement.\n        if (node === newest) {\n          hand = undefined\n        }\n        cache.delete(node.key)\n      }\n      const entry: Node = { key, value, visited: false }\n      newest = entry\n      cache.set(key, entry)\n    },\n    clear() {\n      cache.clear()\n      hand = undefined\n      newest = undefined\n    },\n  }\n}\n"],"mappings":";;;;;;;;;;;;;AAkBA,SAAgB,iBACd,KAC0B;CAM1B,MAAM,wBAAQ,IAAI,IAAgB;CAClC,IAAI;CACJ,IAAI;CAEJ,OAAO;EACL,IAAI,KAAK;GACP,MAAM,QAAQ,MAAM,IAAI,GAAG;GAC3B,IAAI,CAAC,OACH;GAEF,MAAM,UAAU;GAChB,OAAO,MAAM;EACf;EACA,IAAI,KAAK,OAAO;GACd,MAAM,WAAW,MAAM,IAAI,GAAG;GAC9B,IAAI,UAAU;IACZ,SAAS,QAAQ;IACjB;GACF;GACA,IAAI,MAAM,QAAQ,KAAK;IAGrB,IAAI,OAAO,MAAM,KAAK,EAAE;IACxB,OAAO,CAAC,QAAQ,KAAK,SAAS;KAC5B,IAAI,MACF,KAAK,UAAU;UAEf,OAAO,MAAM,OAAO;KAEtB,OAAO,KAAM,KAAK,EAAE;IACtB;IAGA,IAAI,SAAS,QACX,OAAO,KAAA;IAET,MAAM,OAAO,KAAK,GAAG;GACvB;GACA,MAAM,QAAc;IAAE;IAAK;IAAO,SAAS;GAAM;GACjD,SAAS;GACT,MAAM,IAAI,KAAK,KAAK;EACtB;EACA,QAAQ;GACN,MAAM,MAAM;GACZ,OAAO,KAAA;GACP,SAAS,KAAA;EACX;CACF;AACF"}