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

ocache

v0.1.4

Published

Standalone caching utilities with TTL, SWR, and HTTP response caching

Readme

ocache

npm version npm downloads

Usage

Caching Functions

Wrap any function with defineCachedFunction to add caching with TTL, stale-while-revalidate, and request deduplication:

import { defineCachedFunction } from "ocache";

const cachedFetch = defineCachedFunction(
  async (url: string) => {
    const res = await fetch(url);
    return res.json();
  },
  {
    maxAge: 60, // Cache for 60 seconds
    name: "api-fetch",
  },
);

// First call hits the function, subsequent calls return cached result
const data = await cachedFetch("https://api.example.com/data");

Options

const cached = defineCachedFunction(fn, {
  name: "my-fn", // Cache key name (defaults to function name)
  maxAge: 10, // TTL in seconds (default: 1)
  swr: true, // Stale-while-revalidate (default: true)
  staleMaxAge: 60, // Max seconds to serve stale content
  base: "/cache", // Base prefix for cache keys (string or string[] for multi-tier)
  group: "my-group", // Cache key group (default: "functions")
  getKey: (...args) => "custom-key", // Custom cache key generator
  shouldBypassCache: (...args) => false, // Skip cache entirely when true
  shouldInvalidateCache: (...args) => false, // Force refresh when true
  validate: (entry) => entry.value !== undefined, // Custom validation
  transform: (entry) => entry.value, // Transform before returning
  onError: (error) => console.error(error), // Error handler
});

Caching HTTP Handlers

Wrap HTTP handlers with defineCachedHandler for automatic response caching with etag, last-modified, and 304 Not Modified support:

import { defineCachedHandler } from "ocache";

const handler = defineCachedHandler(
  async (event) => {
    // event.req is a standard Request object
    const url = event.url ?? new URL(event.req.url);
    const data = await getExpensiveData(url.pathname);
    return new Response(JSON.stringify(data), {
      headers: { "content-type": "application/json" },
    });
  },
  {
    maxAge: 300, // Cache for 5 minutes
    swr: true,
    staleMaxAge: 600,
    varies: ["accept-language"], // Vary cache by these headers
  },
);

Headers-only Mode

Use headersOnly to handle conditional requests without caching the full response:

const handler = defineCachedHandler(myHandler, {
  headersOnly: true,
  maxAge: 60,
});

Cache Invalidation

Cached functions have an .invalidate() method that removes cached entries across all base prefixes:

import { defineCachedFunction } from "ocache";

const getUser = defineCachedFunction(async (id: string) => db.users.find(id), {
  name: "getUser",
  maxAge: 60,
  getKey: (id: string) => id,
});

const user = await getUser("user-123");

// Invalidate a specific entry
await getUser.invalidate("user-123");

// Next call will re-invoke the function
const freshUser = await getUser("user-123");

You can also use the standalone invalidateCache() when you don't have a reference to the cached function — just pass the same options:

import { invalidateCache } from "ocache";

await invalidateCache({
  options: { name: "getUser", getKey: (id: string) => id },
  args: ["user-123"],
});

For advanced use cases, .resolveKeys() returns the raw storage keys:

const keys = await getUser.resolveKeys("user-123");
// ["/cache:functions:getUser:user-123.json"]

Multi-tier Caching

Use an array of base prefixes to enable multi-tier caching. On read, each prefix is tried in order and the first hit is used. On write, the entry is written to all prefixes:

const cachedFetch = defineCachedFunction(
  async (url: string) => {
    const res = await fetch(url);
    return res.json();
  },
  {
    maxAge: 60,
    base: ["/tmp", "/cache"],
  },
);

This is useful for layered cache setups (e.g., fast local cache + shared remote cache) where you want reads to prefer the nearest tier while keeping all tiers populated on writes.

Custom Storage

By default, ocache uses an in-memory Map-based storage. You can provide a custom storage implementation:

import { setStorage } from "ocache";
import type { StorageInterface } from "ocache";

const redisStorage: StorageInterface = {
  get: async (key) => {
    return JSON.parse(await redis.get(key));
  },
  set: async (key, value, opts) => {
    // Setting null/undefined deletes the entry (used for cache invalidation)
    if (value === null || value === undefined) {
      await redis.del(key);
      return;
    }
    await redis.set(key, JSON.stringify(value), opts?.ttl ? { EX: opts.ttl } : undefined);
  },
};

setStorage(redisStorage);

API

cachedFunction

const cachedFunction = defineCachedFunction;

Alias for defineCachedFunction.


createMemoryStorage

function createMemoryStorage(): StorageInterface;

Creates an in-memory storage backed by a Map with optional TTL support (in seconds).


defineCachedFunction

function defineCachedFunction<T, ArgsT extends unknown[] = any[]>(
  fn: (...args: ArgsT) => T | Promise<T>,
  opts: CacheOptions<T, ArgsT> =

Wraps a function with caching support including TTL, SWR, integrity checks, and request deduplication.

Parameters:

  • fn — The function to cache.
  • opts — Cache configuration options.

Returns: — A cached function with a .resolveKey(...args) method for cache key resolution.


defineCachedHandler

function defineCachedHandler<E extends HTTPEvent = HTTPEvent>(
  handler: EventHandler<E>,
  opts: CachedEventHandlerOptions<E> =

Wraps an HTTP event handler with response caching.

Automatically generates cache keys from the URL path and variable headers, sets cache-control, etag, and last-modified headers, and handles 304 Not Modified responses via conditional request headers.

Parameters:

  • handler — The event handler to cache.
  • opts — Cache and HTTP-specific configuration options.

Returns: — A new event handler that serves cached responses when available.


EventHandler

type EventHandler<E extends HTTPEvent = HTTPEvent> = (

Handler function that receives an HTTPEvent and returns a response value.


invalidateCache

async function invalidateCache<ArgsT extends unknown[] = any[]>(
  input:

Invalidates (removes) cached entries for given arguments and cache options across all base prefixes.

Uses the same key derivation as defineCachedFunction / resolveCacheKeys.

Parameters:

  • input — Object with options (cache options) and optional args (function arguments).

Example:

// Invalidate a specific cached entry
await invalidateCache({
  options: { name: "fetchUser", getKey: (id: string) => id },
  args: ["user-123"],
});

resolveCacheKeys

async function resolveCacheKeys<ArgsT extends unknown[] = any[]>(
  input:

Resolves all cache storage keys (one per base prefix) for given arguments and cache options.

Uses the same key derivation as defineCachedFunction internally:

  • When opts.getKey is provided, it is called with args to produce the key segment.
  • Otherwise, args are hashed with ohash (same default as defineCachedFunction).

Pass the same getKey, name, group, and base options you use in defineCachedFunction / defineCachedHandler to get the exact storage keys.

Parameters:

  • input — Object with options (cache options) and optional args (function arguments).

Returns: — An array of storage key strings (one per base prefix).

Example:

const keys = await resolveCacheKeys({
  options: { name: "fetchUser", getKey: (id: string) => id },
  args: ["user-123"],
});
for (const key of keys) {
  await useStorage().set(key, null); // invalidate all tiers
}

setStorage

function setStorage(storage: StorageInterface): void;

Sets a custom storage implementation to be used by all cached functions.


useStorage

function useStorage(): StorageInterface;

Returns the current storage instance. If none has been set via setStorage, lazily initializes an in-memory storage.

Development

  • Clone this repository
  • Install latest LTS version of Node.js
  • Enable Corepack using corepack enable
  • Install dependencies using pnpm install
  • Run interactive tests using pnpm dev

License

Published under the MIT license 💛.