Skip to content

chaitanyax/bridge-cache

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bridge-cache

A production-ready in-memory cache for Node.js applications.

It is designed as a bridge for frequently requested data such as API responses, DB query results, computed objects, and JSON data.

Why this package

  • Fast in-memory reads for hot data.
  • Automatic invalidation by default (safe behavior if you forget options).
  • Per-key invalidation options (ttlMs, absoluteExpiryAt, tags).
  • Capacity controls with eviction strategies (lru / fifo).
  • Stampede protection with getOrSet (single-flight loading).
  • Small API surface, easy to adopt.

Install

npm install bridge-cache

Quick start

const { createCache } = require("bridge-cache");

const cache = createCache();

cache.set("app:config", { featureA: true });
const config = cache.get("app:config");

console.log(config);

If you call set without options, default TTL invalidation applies automatically (default: 5 minutes).

Core concepts

  • set stores value with optional invalidation options.
  • get returns value on hit, undefined on miss/expired.
  • getOrSet fetches once for concurrent callers and stores result.
  • invalidateTag invalidates grouped keys.
  • invalidateWhere invalidates keys by custom predicate.
  • stats gives counters and hit rate.

API

createCache(options?)

Creates an in-memory cache instance.

const cache = createCache({
  defaultTtlMs: 5 * 60 * 1000,
  maxEntries: 10_000,
  maxSizeBytes: 64 * 1024 * 1024,
  sweepIntervalMs: 60_000,
  evictionPolicy: "lru",
  cloneOnGet: false,
  cloneOnSet: false,
  onEvent: (event) => {
    // Optional telemetry hook
    console.log(event.type, event.key);
  },
});

Options:

  • defaultTtlMs (default 300000): fallback expiry when set has no ttl options.
  • maxEntries (default 10000): maximum number of entries.
  • maxSizeBytes (default 67108864): maximum total cache size.
  • sweepIntervalMs (default 60000): periodic cleanup interval for expired entries.
  • evictionPolicy (default "lru"): "lru" or "fifo".
  • cloneOnGet / cloneOnSet: optional defensive cloning.
  • onEvent: callback for cache events (hit, miss, set, delete, expired, evicted, clear).

set(key, value, options?)

Stores a value.

cache.set("user:1", { id: 1, name: "Ana" }, { ttlMs: 60_000, tags: ["user"] });

Set options:

  • ttlMs: relative expiry in milliseconds.
  • absoluteExpiryAt: absolute Unix epoch timestamp in milliseconds.
  • tags: label list for grouped invalidation.

Behavior:

  • If absoluteExpiryAt is provided, it is used.
  • Else if ttlMs is provided, it is used.
  • Else defaultTtlMs is applied automatically.

get(key) and has(key)

const value = cache.get("user:1");
if (cache.has("user:1")) {
  // key exists and is not expired
}

Expired entries are invalidated automatically on access.

delete(key) and clear()

cache.delete("user:1");
cache.clear();

invalidateTag(tag)

cache.set("user:1", { id: 1 }, { tags: ["tenant:a", "user"] });
cache.set("user:2", { id: 2 }, { tags: ["tenant:a", "user"] });

const removed = cache.invalidateTag("tenant:a");
console.log(removed); // 2

invalidateWhere(predicate)

cache.invalidateWhere((entry, key) => key.startsWith("product:") && entry.accessCount === 0);

getOrSet(key, loader, options?)

Prevents duplicate concurrent loads for the same key.

const profile = await cache.getOrSet(
  "user:1:profile",
  async () => {
    // Only one caller runs this loader concurrently per key
    return fetchUserProfileFromDB(1);
  },
  { ttlMs: 120_000, tags: ["user", "profile"] }
);

stats()

console.log(cache.stats());
// {
//   hits, misses, sets, deletes,
//   evictions, expirations,
//   count, sizeBytes, hitRate
// }

stop()

Stops the internal sweeper interval (recommended in tests/short-lived workers).

cache.stop();

End-to-end usage examples

1. Cache DB results by id

const { createCache } = require("bridge-cache");

const cache = createCache({ defaultTtlMs: 30_000 });

async function getUser(id) {
  return cache.getOrSet(`user:${id}`, async () => {
    return db.users.findById(id);
  }, { tags: ["user"] });
}

2. Cache external API response

const weatherCache = createCache({ defaultTtlMs: 60_000 });

async function getWeather(city) {
  return weatherCache.getOrSet(`weather:${city}`, async () => {
    const res = await fetch(`https://api.example.com/weather?city=${city}`);
    return res.json();
  }, { tags: ["weather"] });
}

3. Invalidate related data after write

async function updateUser(userId, payload) {
  await db.users.update(userId, payload);
  cache.invalidateTag("user");
}

Complete Example

Create a file named example.js:

const { createCache } = require('bridge-cache');

async function run() {
  // 1. Initialize the cache 
  const cache = createCache({
    defaultTtlMs: 60000, // 1 minute
    maxEntries: 100,
    evictionPolicy: 'lru'
  });

  // 2. Set some generic data
  cache.set('user:123', { name: "Alice", active: true }, { tags: ['users'] });
  console.log("User:", cache.get('user:123'));

  // 3. getOrSet pattern (only executes loader if cache misses)
  const apiData = await cache.getOrSet('weather:nyc', async () => {
    console.log("Simulating slow API call...");
    await new Promise(resolve => setTimeout(resolve, 500)); // Sleep
    return { temp: 72, condition: "Sunny" };
  }, { ttlMs: 15000 });

  console.log("Weather:", apiData);

  // 4. Invalidation
  const removed = cache.invalidateTag('users');
  console.log(`Removed ${removed} entries.`);
  console.log("User after invalidation:", cache.get('user:123')); // undefined

  // 5. Cleanup memory interval at process exit
  cache.stop();
}

run();

Data support

Supported well in v1:

  • JSON-friendly objects
  • arrays
  • strings
  • numbers
  • booleans
  • null

Notes:

  • undefined values are rejected.
  • Circular references are rejected (size estimation requires serialization).

Default invalidation strategy

If no invalidation options are passed in set, the package still invalidates automatically:

  • Applies defaultTtlMs (5 minutes by default).
  • Periodic sweep removes expired entries.
  • Capacity checks evict entries when limits are exceeded.

This prevents accidental never-expiring cache entries.

Production recommendations

  • Start with:
    • defaultTtlMs: 300000
    • maxEntries: 10000
    • maxSizeBytes: 64 * 1024 * 1024
    • evictionPolicy: "lru"
  • Add tags for domain-based invalidation (user, catalog, tenant:x).
  • Export stats() to logs/metrics for capacity tuning.
  • Use getOrSet for expensive loaders to prevent traffic spikes.

Development

npm install
npm run build
npm test

Roadmap ideas

  • Redis adapter.
  • Stale-while-revalidate mode.
  • Namespace support.
  • Multi-process invalidation hooks.

Detailed design document

Implementation deep dive is available at:

SYSTEM_DESIGN.md

About

A production-ready in-memory cache for Node.js applications. It is designed as a bridge for frequently requested data such as API responses, DB query results, computed objects, and JSON data.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors