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

@nxtedition/cache

v2.0.1

Published

An async cache with SQLite persistence, in-memory LRU, stale-while-revalidate, and automatic request deduplication.

Readme

@nxtedition/cache

An async cache with SQLite persistence, in-memory LRU, stale-while-revalidate, and automatic request deduplication.

Features

  • Two-tier storage: In-memory LRU cache backed by SQLite on disk
  • Stale-while-revalidate: Serve stale data while refreshing in the background
  • Request deduplication: Concurrent fetches for the same key share a single in-flight request
  • Async value resolution: Transparently fetches missing values via a user-defined valueSelector
  • Buffer support: Store and retrieve binary data (Buffer, Uint8Array) alongside JSON values
  • Size-bounded SQLite: Configurable max database size with automatic eviction of oldest entries

Usage

import { AsyncCache } from '@nxtedition/cache'

const cache = new AsyncCache(
  './my-cache.db', // SQLite file path, or ':memory:'
  async (id: string) => {
    // fetch the value for this key
    const res = await fetch(`https://api.example.com/items/${id}`)
    return res.json()
  },
  (id: string) => id, // keySelector: derive cache key from arguments
  {
    ttl: 60_000, // 60s before value is considered stale
    stale: 30_000, // serve stale for 30s while revalidating
  },
)

const result = cache.get('item-123')

if (result.async) {
  // Cache miss — value is being fetched
  const value = await result.value
} else {
  // Cache hit — value returned synchronously
  const value = result.value
}

API

new AsyncCache(location, valueSelector, keySelector, opts?)

| Parameter | Type | Description | | --------------- | ------------------------------ | --------------------------------------------- | | location | string | SQLite database path, or ':memory:' | | valueSelector | (...args) => V \| Promise<V> | Function to fetch a value on cache miss | | keySelector | (...args) => string | Function to derive a cache key from arguments | | opts | AsyncCacheOptions<V> | Optional configuration |

Options

| Option | Type | Default | Description | | ---------- | ---------------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------- | | ttl | number \| (value, key) => number | MAX_SAFE_INTEGER | Time-to-live in milliseconds. After this, the entry is stale. | | stale | number \| (value, key) => number | MAX_SAFE_INTEGER | Stale-while-revalidate window in milliseconds. After ttl + stale, the entry is purged. | | memory | MemoryOptions \| false \| null | { maxSize: 16MB, maxCount: 16384 } | In-memory cache options, or false/null to disable in-memory caching. | | database | DatabaseOptions \| false \| null | { timeout: 20, maxSize: 256MB } | SQLite options, or false/null to disable persistence. |

MemoryOptions

| Option | Type | Default | Description | | ---------- | -------- | -------------------------- | ---------------------------------------------- | | maxSize | number | 16 * 1024 * 1024 (16 MB) | Maximum total size in bytes of cached entries. | | maxCount | number | 16 * 1024 (16384) | Maximum number of entries in memory. |

DatabaseOptions

| Option | Type | Default | Description | | --------- | -------- | ---------------------------- | ----------------------------------------------------------------- | | timeout | number | 20 | SQLite busy timeout in milliseconds. | | maxSize | number | 256 * 1024 * 1024 (256 MB) | Maximum database file size. Oldest entries are evicted when full. |

CacheResult<V>

Both get() and peek() return a CacheResult<V>, a discriminated union on the async property:

| async | value | Meaning | | ------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | false | V \| undefined | Cache hit — the value is available synchronously. Also returned for stale entries (a background refresh is triggered automatically). undefined when peek() has no cached entry. | | true | Promise<V> | Cache miss — value is a Promise that resolves when the valueSelector completes. |

const result = cache.get('key')

if (result.async) {
  // miss — await the fetch
  const value = await result.value
} else {
  // hit (fresh or stale) — use directly
  const value = result.value
}

Methods

cache.get(...args): CacheResult<V>

Returns a cached value or triggers a fetch on cache miss.

cache.peek(...args): CacheResult<V>

Same as get() but does not trigger a refresh on cache miss. Returns { value: undefined, async: false } for missing entries.

cache.refresh(...args): Promise<V>

Triggers a fetch via valueSelector regardless of cache state. If a fetch for the same key is already in-flight (from a prior get() or refresh()), the existing promise is returned instead of starting a new one.

cache.delete(...args): void

Remove an entry from the cache. The cache key is derived from args via the keySelector, just like get() and refresh(). Also cancels any in-flight deduplication for that key, meaning a pending fetch will not write its result to the cache.

cache.purgeStale(): void

Remove all expired entries from both the in-memory cache and SQLite.

cache.close(): void

Close the SQLite database and release resources.

Deduplication

Concurrent calls to get() or refresh() for the same key share a single in-flight Promise. The valueSelector is called only once, and all callers receive the same resolved value.

// valueSelector is called once, both promises resolve to the same value
const [a, b] = await Promise.all([cache.get('key').value, cache.get('key').value])

If a fetch fails, the deduplication entry is cleaned up and subsequent calls will retry.

Calling cache.delete(key) while a fetch is in-flight invalidates the deduplication entry. The pending promise still resolves for its callers, but the result is not written to the cache.

Stale-While-Revalidate

When an entry's TTL has expired but is still within the stale window (ttl + stale), get() returns the stale value synchronously (async: false) while triggering a background refresh.

Once the stale window expires, the entry is purged entirely and the next get() returns async: true.

|--- ttl ---|--- stale ---|
     fresh      stale       expired

Off-Peak Purge

All cache instances listen for messages on the nxt:offPeak BroadcastChannel. When a message is received, purgeStale() is called on every active instance, allowing coordinated cleanup during low-traffic periods.

Scripts

npm test                # run tests
npm run test:coverage   # run tests with branch coverage report (90%+ enforced)
npm run typecheck       # type-check without emitting
npm run build           # build for publishing