oblivious-set review
oblivious-set 2.0.0 remembers whether a value was added within one fixed time-to-live. It stores a timestamp in a Map, refreshes that timestamp on another `add`, and rejects old entries when `has` checks them. Cleanup is lazy, so it avoids a timer per entry and can be garbage-collected with its owner. Version 2 requires Node 16 or newer and ships declarations plus ESM and CommonJS entry points. It stores no associated value, has no capacity bound, and does not fire an event at expiry, which makes it a recent-ID filter rather than a general cache.
Our oblivious-set 2.0.0 install took 0.7 seconds and bundled to 0.4 KB gzipped with no dependencies, so it is a cheap recent-ID filter for one process and one TTL. Use a bounded cache or shared store when values, capacity, durable deduplication, or exact expiry behavior matter.
We installed it
| Install | ✓ · 0.7s | 1 package on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package with exports map |
| Browser | 0.4 KB | gzipped (0.6 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does oblivious-set install cleanly?
Yes. In a fresh container with an empty cache, npm install oblivious-set finished in 0.7s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.
How much does oblivious-set add to a browser bundle?
0.4 KB gzipped (0.6 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does oblivious-set work with both ESM and CommonJS?
Yes. Both import 'oblivious-set' and require('oblivious-set') worked in Node 22 in our run. The package is published as CommonJS with an exports map.
Does oblivious-set include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
oblivious-set or lru-cache: which should you use?
lru-cache: Use it for key-value entries, TTLs, a capacity limit, disposal hooks, and LRU eviction. Our oblivious-set 2.0.0 install took 0.7 seconds and bundled to 0.4 KB gzipped with no dependencies, so it is a cheap recent-ID filter for one process and one TTL.
When should you not use oblivious-set?
Entries carry data; this package stores only membership timestamps, while an LRU cache stores key-value pairs
Use it if
- A process needs to suppress duplicate event IDs for one fixed number of milliseconds
- Membership is enough and no value needs to be returned with the key
- Lazy cleanup is preferable to keeping an interval or timeout for each entry
- The same tiny typed helper must work from ESM and CommonJS
- Entries carry data; this package stores only membership timestamps, while an LRU cache stores key-value pairs
- Memory must stay under a hard limit; every unexpired key remains in the Map and there is no maximum size
- Expiry must trigger work at an exact deadline; no timer fires when the TTL elapses
- Different entries need different TTLs, callbacks, persistence, or shared state across processes; the API provides none of those
- You expect the full Set contract; the wrapper has no supported single-item delete or iterator, and object keys use reference identity
Setup reality
Our install of oblivious-set 2.0.0 completed in 0.7 seconds. It left 1 package and 1 MB on disk; the package was 164 KB unpacked with 0 direct dependencies and 0 peer dependencies. npm audit found 0 known vulnerabilities. It is CommonJS with an exports map, and both require() and ESM import worked in our Node 22 sandbox. TypeScript declarations were bundled. The esbuild browser result was 0.6 KB minified and 0.4 KB gzipped.
Construction takes one TTL in milliseconds, applied to every entry. Re-adding a value replaces its timestamp and moves it to the newest Map position. A second TTL window needs a second ObliviousSet instance. Objects follow Map identity: the same reference matches, while a new object with identical fields does not. Store primitive IDs when field equality is intended.
Expiration is deliberately lazy. has(value) checks the stored timestamp and removes that value if it is too old. add(value) schedules a zero-delay sweep instead of a timer at the actual expiry time. An expired key can therefore remain in the public map until a later check or cleanup, even though has correctly returns false. The raw map.size is not a guaranteed count of live entries.
There is no maximum capacity, persistence, cross-process coordination, expiration callback, or supported single-key delete method. A burst can retain every distinct key for the full TTL. The README also states that GitHub issues are closed and asks bug reporters to submit a pull request with a reproducing test. Use this only for process-local, best-effort duplicate suppression where losing all history on restart is acceptable.
Patterns
Remember one value for 30 seconds remember-value
import { ObliviousSet } from 'oblivious-set';
const recent = new ObliviousSet<string>(30_000);
recent.add('event-42');
console.log(recent.has('event-42'));The 30,000 value is milliseconds and applies to every entry in this instance.
Drop a repeated event ID suppress-duplicate
const seen = new ObliviousSet<string>(5 * 60_000);
function acceptEvent(id: string) {
if (seen.has(id)) return false;
seen.add(id);
return true;
}This remembers IDs only inside the current process. A restart or a second worker has a separate history.
Refresh age on repeated activity refresh-ttl
const active = new ObliviousSet<string>(60_000);
active.add('session-a');
active.add('session-a');The second `add` writes a new timestamp, so the 60-second window starts again.
Forget every recent value clear-set
const recent = new ObliviousSet<string>(10_000);
recent.add('a');
recent.add('b');
recent.clear();
console.log(recent.has('a'));`clear` removes the whole Map. Version 2 has no supported wrapper method for deleting only `a`.
Check after the TTL observe-expiry
const short = new ObliviousSet<string>(100);
short.add('token');
setTimeout(() => {
console.log(short.has('token')); // false
}, 150);No timer runs at 100 ms. The later `has` call notices the timestamp, deletes the key, and returns false.
Sweep before reading the public Map inspect-size
import { ObliviousSet, removeTooOldValues } from 'oblivious-set';
const recent = new ObliviousSet<string>(1_000);
recent.add('job-1');
removeTooOldValues(recent);
console.log(recent.map.size);The public Map can retain expired entries until cleanup. Depending on it also couples code to an implementation detail.
Use stable object references compare-objects
const recent = new ObliviousSet<{ id: number }>(5_000);
const item = { id: 1 };
recent.add(item);
console.log(recent.has(item)); // true
console.log(recent.has({ id: 1 })); // falseMap compares objects by identity. Put `id` in a string or number set when field equality is required.
Create one set per TTL window separate-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 belongs to the instance, not the entry. Two policies require two sets.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| lru-cache | npm | Use it for key-value entries, TTLs, a capacity limit, disposal hooks, and LRU eviction. |
| quick-lru | npm | Use it for a smaller bounded key-value cache with maximum age support. |
| mnemonist | npm | Use it when this set is one of several specialized collections the application needs. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

