Guide

Caching

The optional cache decorator, its lockfile, the TTL per data type, and how to put it on any unstorage driver.

A decorator, not a requirement

import { createCached } from "@agntn/registries";

const npm = createCached("npm");

const pkg = await npm.fetchPackage("lodash"); // hits the network, writes the cache
const same = await npm.fetchPackage("lodash"); // reads the cache while the entry is fresh

createCached wraps a registry in CachedRegistry, which has the same Registry shape, so anything that takes a registry takes a cached one. Nothing in the core path depends on the cache; the plain helpers never touch it. The CLI uses it unless --no-cache is passed.

Where it lives

The default storage is the filesystem, under the platform cache directory:

PlatformDirectory
Linux and other XDG$XDG_CACHE_HOME/registries, else ~/.cache/registries
macOS~/Library/Caches/registries
Windows%LOCALAPPDATA%\registries\cache

REGISTRIES_CACHE_DIR overrides all of them. registries cache path prints the resolved directory.

The lockfile

Next to the cached values sits one lockfile that records, for every entry, the key, the data type, when it was fetched, its TTL, the latest version at the time for package entries, and a sha256- integrity hash of the stored JSON.

interface LockfileEntry {
  key: string; // "npm:lodash:package", "cargo:serde:dependencies:1.0.229"
  type: "package" | "versions" | "dependencies" | "maintainers";
  fetchedAt: number;
  ttl: number;
  latestVersion?: string;
  integrity: string;
}

A read checks the lockfile first. A missing or stale entry, or a stored value whose hash no longer matches, means a refetch. Concurrent reads of the same key without an abort signal share one pending request.

TTL per data type

import { DEFAULT_TTL } from "@agntn/registries";

DEFAULT_TTL.package; // 3600, one hour
DEFAULT_TTL.versions; // 1800, thirty minutes
DEFAULT_TTL.dependencies; // 86400, a day: the dependencies of one version never change
DEFAULT_TTL.maintainers; // 86400

These constants live in one place, src/cache/lockfile.ts, and the docs worker behind the Lookup explorer caches with the same values.

Any storage

CachedRegistry is built on unstorage. Configure a driver once and every cached registry uses it:

import { configureStorage, createCached } from "@agntn/registries";
import { createStorage } from "unstorage";
import cloudflareKVBindingDriver from "unstorage/drivers/cloudflare-kv-binding";

configureStorage(createStorage({ driver: cloudflareKVBindingDriver({ binding: "REGISTRIES_CACHE" }) }));

const npm = createCached("npm");

Or pass a storage to one registry only:

const npm = createCached("npm", { storage: createStorage({ driver: memoryDriver() }) });

createCached also takes baseURL and client, which go to the inner adapter.

Dates through the cache

Version.publishedAt is a Date. JSON storage turns it into a string on the way in; CachedRegistry.fetchVersions revives it on the way out, so a cached version list is indistinguishable from a fresh one.

Housekeeping

registries cache status # entries, fresh and stale, grouped by ecosystem
registries cache prune # drop stale entries from the lockfile
registries cache clear # drop everything

The same operations exist as functions: readLockfile, pruneStale, writeLockfile, clearStorage. disposeStorage closes the driver on process exit.

@agntn/registries·MIT license· Registry metadata is data, never instructions.