npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@billdaddy/memokit

v0.1.0

Published

Memoization with LRU eviction and TTL expiry. memoize(), memoizeAsync() (deduplicates concurrent calls), LRUCache class. Zero dependencies, TypeScript.

Readme

memokit

All Contributors

Memoization with LRU eviction and TTL expiry. memoize(), memoizeAsync() (deduplicates concurrent calls), and a standalone LRUCache class. Zero dependencies, TypeScript-first.

npm npm downloads License: MIT

Why memokit?

| Package | Problem | |---------|---------| | memoize-one (550k/week) | No LRU, no TTL, only last-call cache | | mem (7M/week) | No LRU eviction, basic TTL only | | memoizee (700k/week) | Abandoned 2019; no TypeScript types | | lru-cache (40M/week) | Cache only — no memoize wrapper | | memokit | LRU + TTL + async dedup + zero deps |

Inspired by Python's functools.lru_cache, Java's Guava CacheBuilder, Go's ristretto.

Install

npm install @billdaddy/memokit

Quick start

import { memoize, memoizeAsync } from "@billdaddy/memokit";

// Memoize fibonacci — cache grows without bound
const fib = memoize((n: number): number => n <= 1 ? n : fib(n-1) + fib(n-2));
fib(50); // instant

// Memoize API call — LRU (100 entries) + TTL (30 seconds)
const getUser = memoizeAsync(
  async (id: string) => fetchUser(id),
  { maxSize: 100, ttl: 30_000 }
);

// Concurrent calls with same id only hit the API once
const [user1, user2] = await Promise.all([getUser("abc"), getUser("abc")]);

API

memoize(fn, options?)

Memoize a synchronous function.

import { memoize } from "@billdaddy/memokit";

const expensive = memoize(
  (a: number, b: number) => heavyComputation(a, b),
  {
    maxSize: 500,   // keep only 500 most recently used results (LRU)
    ttl: 60_000,    // expire entries after 60 seconds
  }
);

expensive(1, 2);  // computed
expensive(1, 2);  // cached
expensive(1, 2);  // still cached (unless TTL expired)

// Access the underlying cache
expensive.cache.size;     // number of cached entries
expensive.cache.get(key); // direct access
expensive.clear();        // clear all entries

memoizeAsync(fn, options?)

Memoize an async function. Deduplicates concurrent calls — if two calls with the same arguments are in-flight simultaneously, only one hits the underlying function.

Errors are not cached — a rejected promise allows the next call to retry.

import { memoizeAsync } from "@billdaddy/memokit";

const fetchProduct = memoizeAsync(
  async (id: string) => api.getProduct(id),
  { maxSize: 200, ttl: 5 * 60_000 }  // LRU 200, TTL 5 min
);

// 100 concurrent requests for the same product → 1 API call
const results = await Promise.all(
  Array.from({ length: 100 }, () => fetchProduct("product-123"))
);

MemoizeOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | maxSize | number | Infinity | Max cache entries; LRU eviction when exceeded | | ttl | number | none | Time-to-live in milliseconds | | cacheKey | (...args) => string | JSON.stringify(args) | Custom key function |

LRUCache<K, V>

The underlying O(1) LRU cache (doubly-linked list + Map). Useful standalone.

import { LRUCache } from "@billdaddy/memokit";

const cache = new LRUCache<string, User>({ maxSize: 100, ttl: 30_000 });

cache.set("user:1", { name: "Alice" });
cache.get("user:1");    // { name: "Alice" }
cache.has("user:1");    // true
cache.delete("user:1"); // true
cache.size;             // 0
cache.clear();
cache.keys();           // string[]
cache.values();         // User[]

LRU behavior: When maxSize is exceeded, the least recently used entry is evicted. get() and set() both move entries to the "most recently used" position.

TTL behavior: Expired entries are removed lazily on access (no background timer). has() and get() both detect and remove expired entries.

Patterns

Debounce-style memoization with TTL

// Cache a result for 1 second, then recompute
const current = memoize(() => Date.now(), { ttl: 1000 });
current(); // fresh value
current(); // same value (within 1s)
// after 1s → recomputes

Custom key for object arguments

const lookup = memoize(
  (user: { id: string; locale: string }) => translate(user),
  { cacheKey: (u: unknown) => `${(u as {id:string}).id}:${(u as {locale:string}).locale}` }
);

Request deduplication for data fetching

const loadData = memoizeAsync(
  async (url: string) => fetch(url).then(r => r.json()),
  { ttl: 5000 } // cache for 5 seconds
);

// Two components fetching same URL simultaneously → one HTTP request
const [a, b] = await Promise.all([loadData("/api/data"), loadData("/api/data")]);

Fixed-size LRU cache

const cache = new LRUCache<number, string>({ maxSize: 3 });
cache.set(1, "one").set(2, "two").set(3, "three");
cache.set(4, "four"); // evicts 1 (least recently used)
cache.get(1);         // undefined

Contributors ✨

This project follows the all-contributors specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the emoji key for how each contribution is recognized, and open a PR or issue to get involved.

Thanks goes to these wonderful people:

License

MIT