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 🙏

© 2025 – Pkg Stats / Ryan Hefner

simple-cache-id

v1.4.0

Published

A lightweight in-memory cache for Node.js with default TTL, persistent storage, wrap() and fallback() helpers for resilient caching strategies.

Readme

simple-cache-id

npm version npm downloads license CI Coverage

🔑 A lightweight in-memory cache for Node.js with default TTL, persistent storage, and powerful caching strategies: wrap() for cache-first and fallback() for fresh-first with resilient fallback.

Perfect for API caching, external service calls, and offline-first applications. Think of it as a tiny Redis-like cache without any dependencies.


✨ Features

  • Set / Get values with optional TTL (seconds).
  • Global default TTL set in constructor.
  • Auto-expiration with setInterval cleanup.
  • Persistent storage to binary file (.sdb) - cache survives restarts!
  • Shared cache mode - share cache across multiple microservices via HTTP!
  • wrap() helper: fetch from cache or compute if missing.
  • fallback() helper: fetch fresh data first, use cache as fallback if error.
  • Delete, flush, and stats API.
  • Supports both CommonJS (require) and ES6 Modules (import).
  • TypeScript definitions included.
  • Zero dependencies.

📦 Installation

npm install simple-cache-id

🚀 Quick Start

Two Powerful Caching Strategies

const SimpleCache = require("simple-cache-id");
const cache = new SimpleCache(60); // 60s default TTL

// 🎯 wrap() - Cache-first (fast, great for static data)
const user = await cache.wrap("user:1", async () => {
  return await fetchFromDB(1); // Only called if not cached
});

// 🔄 fallback() - Fresh-first (resilient, great for APIs)
const weather = await cache.fallback("weather:jakarta", async () => {
  return await fetchWeatherAPI(); // Tries fresh data first
  // Falls back to cache if API fails ✨
});

Why use fallback()?

  • ✅ Always tries to get fresh data first
  • ✅ Resilient to API failures with automatic cache fallback
  • ✅ Perfect for external APIs, network requests, and real-time data
  • ✅ Provides offline support automatically

🚀 Usage

Basic Usage

CommonJS (require)

const SimpleCache = require("simple-cache-id");

// Default TTL = 5 seconds, check interval = 5 seconds
const cache = new SimpleCache(5, { checkInterval: 5 });

// Set & Get
cache.set("foo", "bar");
console.log(cache.get("foo")); // bar

// Override TTL per key
cache.set("baz", "qux", 1);

// Wrap (sync)
cache.wrap("pi", () => Math.PI).then(console.log); // 3.141592653589793

// Wrap (async)
async function fetchUser(id) {
  console.log("Fetching from DB...");
  return { id, name: "Ali" };
}

(async () => {
  const user1 = await cache.wrap("user:1", () => fetchUser(1), 10);
  console.log(user1); // { id: 1, name: "Ali" }

  const user2 = await cache.wrap("user:1", () => fetchUser(1));
  console.log(user2); // from cache, no DB call
})();

// Clean up when done
cache.destroy();

ES6 Module (import)

import SimpleCache from "simple-cache-id";

// Default TTL = 5 seconds, check interval = 5 seconds
const cache = new SimpleCache(5, { checkInterval: 5 });

// Set & Get
cache.set("foo", "bar");
console.log(cache.get("foo")); // bar

// Override TTL per key
cache.set("baz", "qux", 1);

// Wrap (async)
async function fetchUser(id) {
  console.log("Fetching from DB...");
  return { id, name: "Ali" };
}

const user = await cache.wrap("user:1", () => fetchUser(1), 10);
console.log(user); // { id: 1, name: "Ali" }

// Clean up when done
cache.destroy();

TypeScript

import SimpleCache from "simple-cache-id";

interface User {
  id: number;
  name: string;
}

const cache = new SimpleCache(60);
cache.set("user:1", { id: 1, name: "Alice" });

const user = cache.get<User>("user:1");
console.log(user?.name); // Alice

For more examples, see EXAMPLES.md.

Persistent Mode (New in v1.1.0!)

const SimpleCache = require("simple-cache-id");

// Enable persistent storage with cache name
const cache = new SimpleCache(60, {
  checkInterval: 5,
  persistent: true,
  name: 'my-app-cache', // Required! Creates ./.cache/my-app-cache.sdb
  saveDelay: 3 // Auto-saves 3 seconds after last change (default: 3)
});

// Or use custom path instead of name
const cache2 = new SimpleCache(60, {
  persistent: true,
  persistPath: './custom/path/cache.sdb' // Alternative to 'name'
});

// Set data
cache.set("user:1", { id: 1, name: "Alice" }, 3600); // 1 hour TTL
cache.set("config", { theme: "dark" }, 0); // permanent (no TTL)

// Data is auto-saved 3 seconds after last change
// No need to call destroy() unless you want immediate save

// On next app restart...
const cache3 = new SimpleCache(60, {
  persistent: true,
  name: 'my-app-cache' // Same name = same cache file
});

console.log(cache3.get("user:1")); // { id: 1, name: "Alice" }
console.log(cache3.get("config")); // { theme: "dark" }

Shared Mode - Cache Across Multiple Services

Share cache between multiple microservices using HTTP (zero config, automatic):

// shared-cache.js - Create shared cache instance
const SimpleCache = require("simple-cache-id");
const cache = new SimpleCache(60, { shared: true });
module.exports = cache;

// Service A (separate process)
const cache = require('./shared-cache');
await cache.set('user:1', { name: 'Alice' });

// Service B (separate process)
const cache = require('./shared-cache');
const user = await cache.get('user:1'); // Gets { name: 'Alice' } from Service A!

How it works:

  • First instance automatically starts HTTP server (holds data in memory)
  • All other instances become clients (send requests to server)
  • All methods become async - use await
  • Perfect for microservices architecture

Example:

// Service A
const cache = require('./shared-cache');
await cache.set('products', [...]);
await cache.set('config', {...});

// Service B (different process)
const cache = require('./shared-cache');
const products = await cache.get('products'); // Shared data!
const stats = await cache.stats(); // { keys: 2 }

Backward Compatibility

// Old style (still supported)
const cache = new SimpleCache(5, 10); // (defaultTtl, checkInterval)

// New style (recommended)
const cache = new SimpleCache(5, { checkInterval: 10 });

🧪 Testing

npm test

Jest is used for testing. All tests must pass before publishing.


📂 Project Structure

src/       → main source code
test/      → jest test suite
example/   → usage examples
.github/   → CI workflows

📜 API

new SimpleCache(defaultTtl = 0, options = {})

Create a new cache instance with an optional default TTL (in seconds).

Parameters:

  • defaultTtl (number): Default TTL in seconds (0 = no expiration)
  • options (object):
    • checkInterval (number): Interval to check for expired keys in seconds (default: 5)
    • persistent (boolean): Enable persistent storage to binary file (default: false)
    • name (string): Required if persistent=true - Unique cache name (creates ./.cache/{name}.sdb)
    • persistPath (string): Custom path to binary file (overrides name)
    • saveDelay (number): Debounce delay in seconds before auto-save (default: 3)
    • shared (boolean): Enable shared cache mode via HTTP - all methods become async (default: false)

Important:

  • When persistent: true, you must provide either name or persistPath.
  • When shared: true, all methods return Promises - use await.

Example:

// Using name (recommended)
const cache = new SimpleCache(10, {
  checkInterval: 5,
  persistent: true,
  name: 'user-cache', // Creates ./.cache/user-cache.sdb
  saveDelay: 5 // Auto-saves 5 seconds after last change
});

// Using custom path
const cache2 = new SimpleCache(10, {
  persistent: true,
  persistPath: './data/cache.sdb'
});

// Using shared mode (for microservices)
const cache3 = new SimpleCache(60, {
  shared: true // All methods become async
});

set(key, value, ttl?)

Store a value. TTL overrides the default if provided.

Parameters:

  • key (string): Unique cache key
  • value (any): Value to store
  • ttl (number, optional): TTL in seconds, defaults to constructor TTL

Returns: "OK"

get(key)

Retrieve a value (returns null if not found or expired).

Parameters:

  • key (string): Cache key

Returns: The cached value or null

del(key)

Delete a key (returns 1 if deleted, 0 if not found).

Parameters:

  • key (string): Cache key

Returns: 1 if deleted, 0 if not found

flush()

Clear all keys and stop cleanup interval.

destroy()

Destroy cache instance, stop all intervals, and immediately save to binary file if persistent mode is enabled.

Note: With auto-save (debounced), you don't need to call destroy() on normal exit. However, calling it ensures immediate save and proper cleanup.

stats()

Returns { keys: number }.

Example:

console.log(cache.stats()); // { keys: 5 }

wrap(key, fn, ttl?)

Return value from cache or compute it if missing. fn can be sync or async.

Parameters:

  • key (string): Cache key
  • fn (function): Function to compute value if not cached
  • ttl (number, optional): Override TTL in seconds

Returns: Promise resolving to the cached or computed value

Example:

const result = await cache.wrap("expensive-op", async () => {
  // Expensive computation
  return await fetchFromDatabase();
}, 3600);

fallback(key, fn, ttl?)

Try to get fresh data first, use cache as fallback if error occurs. This is the opposite of wrap() - it prioritizes fresh data over cached data.

Parameters:

  • key (string): Cache key
  • fn (function): Function to fetch fresh data
  • ttl (number, optional): Override TTL in seconds

Returns: Promise resolving to fresh or cached value

Throws: Error if both function fails and no cache available

Behavior:

  1. Try to execute fn() to get fresh data
  2. If successful, cache the result and return it
  3. If failed, try to get from cache
  4. If cache exists, return cached value
  5. If no cache, throw the original error

Example:

// Fetch fresh data from API, use cache if API fails
const weather = await cache.fallback("weather:jakarta", async () => {
  // This might fail due to network issues
  return await fetchWeatherAPI("Jakarta");
}, 300);

Use Cases:

  • API calls with fallback to cached data
  • External service calls with resilience
  • Real-time data with stale-data fallback
  • Network requests with offline support

Comparison:

  • wrap(): Cache-first (fast, may serve stale data)
  • fallback(): Fresh-first (fresh data, resilient to failures)

💾 Persistent Storage

When persistent: true, the cache uses a custom binary format (.sdb file) similar to Redis RDB:

  • On constructor: Automatically loads from binary file (if exists)
  • During runtime: All operations happen in memory only (zero I/O overhead)
  • Auto-save: Debounced save (N seconds after last change, default: 3 seconds)
  • On destroy: Immediately saves snapshot to binary file (only non-expired entries)
  • Graceful shutdown: Automatically saves on SIGINT/SIGTERM/beforeExit

Binary Format:

  • Header: Magic "SDB", version, entry count
  • Entry: Key length, key, expiry time, value JSON length, value JSON
  • Atomic writes using temp file + rename strategy

TTL Handling:

  • Only saves entries with valid TTL (not expired)
  • Expired entries are filtered out on both save and load
  • Supports permanent keys (TTL = 0)

Multiple Instances:

  • Each cache name/path can only have ONE active instance
  • Prevents data corruption from concurrent writes
  • Throws error if file is already in use

🤝 Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

Ways to contribute:

  • Report bugs and suggest features
  • Submit pull requests
  • Improve documentation
  • Develop plugins

📄 License

MIT © 2025