0.10.1 • Published 5 months ago

@httpx/lru v0.10.1

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

@httpx/lru

npm changelog codecov bundles node browserslist downloads license

Install

$ npm install @httpx/lru
$ yarn add @httpx/lru
$ pnpm add @httpx/lru

Features

Documentation

LruCache

LruCache provides a base LRU implementation. It is a simple cache with a fixed capacity. When the cache is full, the least recently used item is removed. Under the hood it uses a doubly linked list implementation to allow get() in O(1).

API

MethodDescription
set(key, value): booleanAdd a new entry and return true if entry was overwritten
get(key): TValue \| undefinedRetrieve a cache entry by key
has(key): booleanCheck if an entry exist
delete(key): booleanRemove an entry, returns bool indicating if the entry was existing
getOrSet(key, valueOrFn): TValueReturn the entry if exists otherwise save a new entry
clear(): numberClear the cache and return the actual number of deleted entries

Usage

// bundle size: ~550B
import { LruCache } from '@httpx/lru';

const lru = new LruCache({ maxSize: 1000 });

lru.set('🦄', ['cool', 'stuff']);

if (lru.has('🦄')) {;
 console.log(lru.get('🦄'));
 // ['cool', 'stuff']
}

lru.delete('🦄');
lru.clear();

TimeLruCache

TimeLruCache allows to work with expiry time (TTL). Time-to-live are expressed in milliseconds. The API is similar to LruCache.

MethodDescription
set(key, value, ttl?): booleanAdd a new entry and return true if entry was overwritten
get(key): TValue \| undefinedRetrieve an entry if exists and hasn't expired.
has(key): booleanCheck if an entry exist and hasn't expired
delete(key): booleanRemove an entry, returns bool indicating if the entry was existing
getOrSet(key, valueOrFn, ttl?): TValueReturn the entry if exists otherwise save a new entry (value or callback)
clear(): numberClear the cache and return the actual number of deleted entries

TimeLruCache.has(key)

Checks whether an entry exist and hasn't expired. If the entry exists but has expired, it will be removed automatically and trigger the onEviction callback if present.

import { TimeLruCache } from '@httpx/lru';

const oneSecondInMillis = 1000;

const lru = new TimeLruCache({
  maxSize: 1,
  defaultTTL: oneSecondInMillis,
  onEviction: () => { console.log('evicted') }
});

lru.set('key0', 'value0', 2 * oneSecondInMillis);

// 👇 Will evict key0 as maxSize is 1 
lru.set('key1', 'value1', 2 * oneSecondInMillis); 

lru.has('key0'); // 👈 false (item does not exists)
lru.has('key1'); // 👈 true  (item is present and is not expired)

const value = lru.get('key1'); // 👈 'value1' (item is present and is not expired)

// 🕛 wait 3 seconds, time for the item to expire

lru.has('key1'); // 👈 false (item is present but expired - 👋 onEviction will be called)

API

Iterable

import { LruCache } from '@httpx/lru';

const lru = new LruCache({ maxSize: 2 });

// 👇 Fill the cache with 3 entries
lru.set('key1', 'value1');
lru.set('key2', 'value2');
lru.set('key3', 'value3'); // 👈 Will evict key1 as maxSize is 2

lru.get('key2'); // 👈 Trigger a get to move key2 to the head

const results = [];

// 🖖 Iterate over the cache entries
for (const [key, value] of lru) {
  results.push([key, value]);
}

expect(results).toStrictEqual([
   ['key3', 'value3'], // 👈  Least recently used first
   ['key2', 'value2'], // 👈  Most recently used last
]);

Callbacks

onEviction callback

Can be useful to clean up resources or trigger side effects. onEviction callback is called right before an entry is evicted.

const fn = vi.fn();

const lru = new LruCache({
  maxSize: 2,
  onEviction: (key, value) => {
    fn(key, value);
  },
});
lru.set('key1', 'value1');
lru.set('key2', 'value2');
lru.set('key3', 'value3'); // 👈 Will evict key1 due to capacity
expect(fn).toHaveBeenCalledExactlyOnceWith('key1', 'value1');

Benchmarks

Performance is continuously monitored thanks to codspeed.io.

CodSpeed Badge

 RUN  v3.1.4 /home/sebastien/github/httpx/packages/lru


 ✓ bench/compare/lru-cache/get.bench.ts > LruCache.get() - 1000 items / maxSize: 1000 3691ms
     name                                            hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · @httpx/lru.get() - ts files (dev)        42,405.13  0.0111  0.3326  0.0236  0.0244  0.0670  0.0747  0.1128  ±0.73%    21203   fastest
   · @httpx/lru.get() - compiled (dist)       38,697.48  0.0125  0.2658  0.0258  0.0279  0.0769  0.0945  0.1628  ±0.97%    19349
   · @httpx/time-lru.get() - ts files (dev)   10,286.54  0.0385  0.7843  0.0972  0.1697  0.2713  0.3135  0.4499  ±2.02%     5144
   · @httpx/time-lru.get() - compiled (dist)  30,573.80  0.0129  0.2965  0.0327  0.0567  0.1081  0.1332  0.2113  ±1.25%    15287
   · quick-lru@7.0.1.get()                     5,094.69  0.0771  1.4515  0.1963  0.3373  0.6360  0.8556  1.0155  ±2.96%     2549   slowest
   · lru-cache@11.1.0.get()                   34,463.75  0.0134  0.3255  0.0290  0.0445  0.0920  0.1093  0.1646  ±1.08%    17232

 ✓ bench/compare/lru-cache/eviction.bench.ts > LruCache.set() 1000 items / maxSize: 500 3049ms
     name                                            hz     min      max    mean     p75     p99    p995    p999      rme  samples
   · @httpx/lru.set() - ts files (dev)         4,509.30  0.0605  14.5395  0.2218  0.2462  0.9715  6.2385  9.3204  ±12.47%     2255   slowest        
   · @httpx/lru.set() - compiled (dist)        6,480.95  0.0475   3.3455  0.1543  0.1856  0.5587  0.6244  1.4191   ±3.01%     3241
   · @httpx/time-lru.set() - compiled (dist)   5,968.81  0.0505   2.7786  0.1675  0.2591  0.5994  0.7967  1.5231   ±3.22%     2985
   · quick-lru@7.0.1.set()                    16,532.55  0.0239   1.1930  0.0605  0.0994  0.2428  0.3688  0.9218   ±2.32%     8267   fastest        
   · lru-cache@11.1.0.set()                    7,367.21  0.0476   2.2883  0.1357  0.2173  0.3905  0.4858  1.5726   ±2.65%     3685

 ✓ bench/compare/lru-cache/set.bench.ts > LruCache.set() 1000 items / maxSize: 1000 3063ms
     name                                            hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · @httpx/lru.set() - ts files (dev)        21,091.59  0.0176  0.2796  0.0474  0.0739  0.1185  0.1394  0.2052  ±1.17%    10547
   · @httpx/lru.set() - compiled (dist)       29,087.80  0.0198  0.2193  0.0344  0.0325  0.0978  0.1051  0.1349  ±0.71%    14544
   · @httpx/time-lru.set() - compiled (dist)  35,151.91  0.0200  0.1908  0.0284  0.0283  0.0769  0.0841  0.0994  ±0.42%    17576
   · quick-lru@7.0.1.set()                    14,710.78  0.0231  1.1529  0.0680  0.0866  0.2288  0.5122  0.9337  ±2.09%     7356   slowest
   · lru-cache@11.1.0.set()                   37,232.72  0.0182  0.5392  0.0269  0.0248  0.0875  0.0951  0.1472  ±0.70%    18617   fastest

 ✓ bench/compare/lru-cache/iterate.bench.ts > LruCache iterator - 1000 items 2444ms
     name                                           hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · @httpx/lru - forEach - ts files (dev)   18,562.39  0.0205  1.6580  0.0539  0.0810  0.1873  0.2947  0.4141  ±1.60%     9282
   · @httpx/lru - forEach - compiled (dist)  27,641.72  0.0204  0.5506  0.0362  0.0377  0.1089  0.1430  0.2972  ±1.04%    13821   fastest
   · quick-lru@7.0.1 - forEach                9,100.31  0.0510  0.8452  0.1099  0.1398  0.4057  0.4698  0.6423  ±2.07%     4552   slowest
   · lru-cache@11.1.0 - forEach              16,448.05  0.0370  0.9403  0.0608  0.0593  0.1687  0.2128  0.3754  ±1.21%     8225

 ✓ bench/compare/lru-cache/peek.bench.ts > LruCache.peek() - 1000 items / maxSize: 1000 2487ms
     name                                        hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · @httpx/lru.peek() - ts files (dev)   98,492.49  0.0073  1.0316  0.0102  0.0098  0.0237  0.0264  0.0357  ±0.67%    49247   fastest
   · @httpx/lru.peek() - compiled (dist)  87,879.22  0.0074  1.6646  0.0114  0.0114  0.0272  0.0286  0.0468  ±1.07%    43940
   · quick-lru@7.0.1.peek()               16,194.20  0.0422  0.9650  0.0618  0.0616  0.1535  0.1643  0.2312  ±0.94%     8098   slowest
   · lru-cache@11.1.0.peek()              59,101.42  0.0125  0.2308  0.0169  0.0169  0.0306  0.0418  0.0495  ±0.27%    29551

 BENCH  Summary

  quick-lru@7.0.1.set() - bench/compare/lru-cache/eviction.bench.ts > LruCache.set() 1000 items / maxSize: 500
    2.24x faster than lru-cache@11.1.0.set()
    2.55x faster than @httpx/lru.set() - compiled (dist)
    2.77x faster than @httpx/time-lru.set() - compiled (dist)
    3.67x faster than @httpx/lru.set() - ts files (dev)

  @httpx/lru.get() - ts files (dev) - bench/compare/lru-cache/get.bench.ts > LruCache.get() - 1000 items / maxSize: 1000
    1.10x faster than @httpx/lru.get() - compiled (dist)
    1.23x faster than lru-cache@11.1.0.get()
    1.39x faster than @httpx/time-lru.get() - compiled (dist)
    4.12x faster than @httpx/time-lru.get() - ts files (dev)
    8.32x faster than quick-lru@7.0.1.get()

  @httpx/lru - forEach - compiled (dist) - bench/compare/lru-cache/iterate.bench.ts > LruCache iterator - 1000 items
    1.49x faster than @httpx/lru - forEach - ts files (dev)
    1.68x faster than lru-cache@11.1.0 - forEach
    3.04x faster than quick-lru@7.0.1 - forEach

  @httpx/lru.peek() - ts files (dev) - bench/compare/lru-cache/peek.bench.ts > LruCache.peek() - 1000 items / maxSize: 1000
    1.12x faster than @httpx/lru.peek() - compiled (dist)
    1.67x faster than lru-cache@11.1.0.peek()
    6.08x faster than quick-lru@7.0.1.peek()

  lru-cache@11.1.0.set() - bench/compare/lru-cache/set.bench.ts > LruCache.set() 1000 items / maxSize: 1000
    1.06x faster than @httpx/time-lru.set() - compiled (dist)
    1.28x faster than @httpx/lru.set() - compiled (dist)
    1.77x faster than @httpx/lru.set() - ts files (dev)
    2.53x faster than quick-lru@7.0.1.set()

See benchmark file for details.

Bundle size

Bundle size is tracked by a size-limit configuration

Scenario (esm)Size (compressed)
import { LruCache } from '@httpx/lru~ 570B
import { TimeLruCache } from '@httpx/lru~ 670B

For CJS usage (not recommended) track the size on bundlephobia.

Compatibility

LevelCIDescription
NodeCI for 18.x, 20.x & 22.x.
BrowserTested with latest chrome (vitest/playwright)
Browserslist> 95% on 01/2025. defaults, chrome >= 96, firefox >= 105, edge >= 113, safari >= 15, ios >= 15, opera >= 103, not dead
EdgeEnsured on CI with @vercel/edge-runtime.
CloudflareEnsured with @cloudflare/vitest-pool-workers (see wrangler.toml
TypescriptTS 5.0 + / are-the-type-wrong checks on CI.
ES2022Dist files checked with es-check
PerformanceMonitored with codspeed.io

For older browsers: most frontend frameworks can transpile the library (ie: nextjs...)

Comparison with other libraries

Contributors

Contributions are welcome. Have a look to the CONTRIBUTING document.

Sponsors

If my OSS work brightens your day, let's take it to new heights together! Sponsor, coffee, or star – any gesture of support fuels my passion to improve. Thanks for being awesome! 🙏❤️

Special thanks to

License

MIT © Sébastien Vanvelthem and contributors.

0.10.1

5 months ago

0.10.0

5 months ago

0.9.2

5 months ago

0.9.1

6 months ago

0.9.0

8 months ago

0.8.1

8 months ago

0.8.0

8 months ago

0.7.0

8 months ago

0.6.0

9 months ago

0.5.0

9 months ago

0.4.1

9 months ago

0.4.0

9 months ago

0.3.0

9 months ago

0.2.1

9 months ago

0.2.0

9 months ago