oblivious-set
oblivious-set is a tiny in-memory set whose entries stop matching after a fixed time-to-live. It stores each value with the time it was added, refreshes that time when the same value is added again, and removes expired entries lazily without keeping one timer per value. It is useful for short-lived duplicate suppression, but it is not a general cache: there are no stored values, size limits, eviction policies, persistence, or per-entry TTLs.
A good fit for process-local recent-ID suppression when membership and one fixed TTL are all you need. Choose a bounded cache instead if stale storage, values, capacity limits, or precise expiration matter.
Use it if
- You need to remember recently seen IDs and answer only yes or no for a single fixed TTL
- You want lazy expiration without creating a timeout or interval for every entry
- You need the same small TypeScript-friendly utility in ESM and CommonJS code
- You want a dependency-free building block and are comfortable with an intentionally tiny API
- You need a cache that returns associated values: ObliviousSet stores only membership timestamps, so lru-cache is a better fit
- You need a hard memory bound or least-recently-used eviction: the implementation has no maximum size and keeps every unexpired entry
- You expect expired entries to disappear at their deadline: cleanup only happens when has checks that value or an add schedules a sweep
- You need different TTLs per item, expiration callbacks, persistence, or shared state across processes: none of those features exist in the API
- You need drop-in Set behavior: add returns void, delete and iteration are not exposed as supported methods, and object membership uses Map identity
Setup reality
Installation is only `npm install oblivious-set`; version 2 has no runtime dependencies, includes declarations, exposes ESM and CommonJS builds, and requires Node 16 or newer. Construction takes one TTL in milliseconds, and that TTL applies to every entry. The important surprise is that expiration is lazy. Calling add schedules one zero-delay cleanup pass, while has removes the requested value if its stored timestamp is too old. There is no timer for the actual expiry deadline, so an entry can remain in the public map after its TTL until it is checked or a later add triggers cleanup. This normally does not affect has, which validates age before returning, but it means map.size is not a reliable count of live values. Re-adding a value refreshes its timestamp and moves it to the end of the internal insertion order. There is no supported delete method on the wrapper, no maximum capacity, no per-item TTL, and no callback when something expires. The map property and removeTooOldValues helper are exported, but using them ties application code to implementation details. The README also says GitHub issues are closed and asks bug reporters to send a pull request with a test case, which raises the support burden for consumers. Keep the instance process-local, choose a bounded TTL, and do not use it where expiry must trigger work at an exact time.
Patterns
Remember a value for a fixed TTLtrack-recent-value
import { ObliviousSet } from 'oblivious-set';
const recent = new ObliviousSet<string>(30_000);
recent.add('event-42');
console.log(recent.has('event-42')); // trueThe constructor value is milliseconds and applies to every entry in this instance.
Suppress duplicate event IDssuppress-duplicates
const seen = new ObliviousSet<string>(5 * 60_000);
function acceptEvent(id: string): boolean {
if (seen.has(id)) return false;
seen.add(id);
return true;
}This is process-local and best-effort; separate processes have separate sets, and a restart forgets every ID.
Refresh an entry by adding it againrefresh-entry-ttl
const active = new ObliviousSet<string>(60_000);
active.add('session-a');
// Later activity resets the stored timestamp.
active.add('session-a');Re-adding deletes and reinserts the map entry, which both refreshes its TTL and moves it to the newest position.
Clear all remembered valuesclear-all-entries
const recent = new ObliviousSet<string>(10_000);
recent.add('a');
recent.add('b');
recent.clear();
console.log(recent.has('a')); // falseclear removes all entries at once; the wrapper does not expose a supported method for deleting only one value.
Let has reject an expired valuecheck-after-expiry
const shortLived = new ObliviousSet<string>(100);
shortLived.add('token');
setTimeout(() => {
console.log(shortLived.has('token')); // false
}, 150);The entry is checked and deleted when has runs; no timer fires at the 100 ms expiry point.
Run the exported cleanup helperclean-expired-entries
import { ObliviousSet, removeTooOldValues } from 'oblivious-set';
const recent = new ObliviousSet<string>(1_000);
recent.add('job-1');
removeTooOldValues(recent);Normal add calls already schedule this helper for the next tick. Calling it directly is mainly useful before inspecting the underlying map.
Clean before reading the internal sizeinspect-live-count
import { ObliviousSet, removeTooOldValues } from 'oblivious-set';
const recent = new ObliviousSet<string>(10_000);
recent.add('a');
recent.add('b');
removeTooOldValues(recent);
const approximateLiveCount = recent.map.size;map is public, but it is an implementation detail and can include expired entries until cleanup runs. There is no official size getter.
Understand object identity membershipuse-object-identity
const recentObjects = new ObliviousSet<{ id: number }>(5_000);
const item = { id: 1 };
recentObjects.add(item);
console.log(recentObjects.has(item)); // true
console.log(recentObjects.has({ id: 1 })); // falseThe internal Map compares objects by reference, not by their fields. Store a stable primitive ID when value equality is required.
Load the CommonJS builduse-commonjs
const { ObliviousSet } = require('oblivious-set');
const recent = new ObliviousSet(30_000);
recent.add('request-7');Version 2 exports both ESM and CommonJS entry points, but requires Node 16 or newer.
Use separate sets for separate TTLsseparate-ttl-windows
const recentRequests = new ObliviousSet<string>(5_000);
const recentLogins = new ObliviousSet<string>(15 * 60_000);
recentRequests.add('request-1');
recentLogins.add('user-9');TTL is configured per set, not per entry. Multiple windows require multiple instances.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| lru-cache | npm | Use it when entries have values and you need TTLs, a maximum size, disposal hooks, or LRU eviction |
| quick-lru | npm | Use it for a compact key-value cache with a maximum size and optional expiration |
| mnemonist | npm | Use it when you need a broader collection library with structures such as LRU caches, heaps, queues, and specialized sets |