Skip to content
Justin SilverTECHNOLOGY, TRAVEL & EVERYTHING IN BETWEEN
Libraries & Tools
Cachingnpm

adaptive-cache

adaptive-cache adjusts cache lifetimes by comparing content fingerprints on writes. Unchanged data can receive a longer TTL, while changed data returns to the initial lifetime under the default policy. Use Express or Fastify adapters for HTTP responses, or AdaptiveCache directly for expensive application work. Redis is the default backend and coordinates state across hosts. Optional clustered LRU storage supports local payload caching or a deployment without Redis. Loader-based APIs add concurrent request coalescing and explicit stale-refresh policies. You still choose cache identity, freshness limits, and which responses are safe to share.

How a result earns its cache lifetime

  1. Identify the resource

    Choose a complete direct API key or let an HTTP adapter construct one. Keep callers with different authorization or response variations separate.

  2. Read or load

    Return an eligible fresh entry. getOrSet() shares concurrent loader work on one instance, with Redis locks adding coordination across instances.

  3. Compare the content

    A successful write compares fingerprints. The default policy grows TTL for unchanged content and resets it for changed content, within your configured maximum.

  4. Refresh or invalidate

    An explicit loader can refresh retained stale data. Clear a key or invalidate tags when application changes require removing cached results sooner.

Start with the published direct API

Use Node.js 22 or newer and make Redis available through REDIS_URL. This example pins the published 0.1.1 release checked for this guide. GitHub main identifies 0.2.0, so check release notes before copying newer configuration options into an existing deployment.

npm install @0xdoublesharp/adaptive-cache@0.1.1

Cache an application calculation

Use a complete key for the weekly report. The loader supplies its value, and a tag gives application code a way to invalidate related reports.

import { AdaptiveCache } from '@0xdoublesharp/adaptive-cache';

const cache = new AdaptiveCache({
  keyPrefix: 'reports:', initialTTL: 10, maxTTL: 300,
});

try {
  const report = await cache.getOrSet(
    'reports:weekly',
    async () => ({ total: 42 }),
    { tags: ['reports'] },
  );
  console.log(report);
  await cache.invalidateTags(['reports']);
} finally {
  await cache.close();
}

Connect HTTP policy to the loader

Install Express or Fastify separately when using its adapter. Middleware caches eligible GET responses and bypasses streams. Authorization and Cookie requests bypass storage by default; enabling them requires keys or variations that isolate the caller. Private responses, no-store, Set-Cookie, and unsupported Vary headers also prevent storage. The package honors these restrictions but does not generate Cache-Control headers. An ordinary route handler is not a background refresh function. Supply an explicit loader when enabling staleWhileRevalidate.

Set limits for the deployment you have

Adaptive TTL options use seconds. Direct keys already include their namespace text; keyPrefix selects coordination state rather than prepending to them. LRU-backed modes require @0xdoublesharp/lru-cache-clustered and lru-cache peers. Clustered LRU is volatile and has weaker lock-release coordination than Redis-backed modes. In l1-redis mode, an L1 hit still validates against Redis. TTL adaptation observes writes, not every upstream change, so cap maxTTL according to the age your application can accept.

Keep building

Find more DoubleSharp packages for application development, testing, and operations.

Libraries & Tools