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

opticore-cache

v1.0.2

Published

Highly configurable caching system compatible with OptiCore.js

Readme

opticore-cache

Enterprise‑grade Universal HTTP Cache for OptiCoreJs and Node.js

npm
version license TypeScript Node

High‑performance, flexible and production‑ready HTTP caching layer
compatible with Express · Fetch · Axios · Native Node HTTP · cURL

Table of Contents

Why opticore-cache?

Modern applications require:

  • High availability
  • Reduced external API load
  • Faster response times
  • Observability & metrics
  • Flexible storage strategies

opticore-cache provides a unified abstraction layer over multiple HTTP clients with an extensible and configurable caching engine.

Key Features

  • Automatic HTTP response caching
  • Multi-client support (Fetch, Axios, Node HTTP, cURL)
  • Memory, Disk or Hybrid storage
  • Per-request TTL override
  • Pattern-based invalidation (wildcards supported)
  • Built-in statistics & hit-rate tracking
  • Namespaces for multi-tenant systems
  • TypeScript-first design
  • Instance lifecycle management (create / destroy)

Installation

npm install opticore-cache

Optional dependencies:

npm install axios express

Quick Start

import { HttpCacheFactory } from "opticore-cache";

const cache = HttpCacheFactory.create("my-app");

const data = await cache.getWithCache("https://api.example.com/users");

console.log(data);

[HttpCacheFactory Usage

The HttpCacheFactory is responsible for:

  • Creating configured cache instances
  • Managing instance reuse
  • Supporting multiple HTTP clients
  • Destroying instances when needed
  • Adapting existing cache services

Create with Fetch (default)

const cache = HttpCacheFactory.create("app-fetch", {
  clientType: "fetch",
  storageType: "disk",
  diskDir: "./storage/cache",
  defaultTTL: 60000
});

Create with Axios

const axiosCache = HttpCacheFactory.create("app-axios", {
  clientType: "axios",
  clientOptions: {
    baseURL: "https://jsonplaceholder.typicode.com",
    timeout: 5000
  }
});

Create with Node HTTP

const nodeCache = HttpCacheFactory.create("app-node", {
  clientType: "node-http",
  clientOptions: {
    timeout: 8000
  }
});

Create with cURL

const curlCache = HttpCacheFactory.create("app-curl", {
  clientType: "curl",
  clientOptions: {
    timeout: 10000
  }
});

Destroy an Instance

HttpCacheFactory.destroy("app-fetch", "fetch");

Adapt Existing Cache

const adapted = HttpCacheFactory.createFromExistingCache(
  customCacheService,
  "custom-namespace"
);

Configuration Options

When creating an instance of the HTTP cache service using HttpCacheFactory.create(), you can pass an options object to customize its behavior. Below are the available options:

| Option | Type | Description | Exemple | |-------------|--------------------|-------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| | storageType | 'memory' ou 'disk' | Defines the storage type: in-memory (faster, volatile) or on disk (persistent). | 'disk' | | diskDir | string | Required if storageType: 'disk'. Path to the directory where cache files will be stored. | 'src/core/cache/app' | | defaultTTL | number | Default time-to-live for cache entries (in milliseconds). Used if no timeToLive is provided in a request. | 60000 (1 minute) | | maxSize | number | Maximum number of entries in the cache. When exceeded, the oldest entries are removed (LRU). | 100 | | namespace | string | (Optional) Namespace used to isolate cache keys. Useful when using multiple instances. | 'external-api' | | serializer | object | (Optional) Overrides the default serialization/deserialization functions (JSON by default). | {{ serialize: JSON.stringify, deserialize: JSON.parse }} |

Example initialization with all options:

const httpCache = HttpCacheFactory.create('external-api', {
    storageType: 'disk',
    diskDir: 'src/core/cache/demo',
    defaultTTL: 10000,        // 10 secondes
    maxSize: 100,
    namespace: 'api-v1'
});

Advanced Usage

The IHttpCacheService service offers several advanced features for fine-grained HTTP cache control.


1. Per-Request Cache Bypass

You can bypass the cache for a specific request using the bypassCache option. This forces an API call and updates the cache with the new response.

const cacheOptions = {
    bypassCache: req.query.bypass === 'true',  // e.g. ?bypass=true
    timeToLive: 60000
};
const data = await httpCache.getWithCache(url, {}, cacheOptions);

2. Custom Time-To-Live

The timeToLive option lets you define a custom expiration duration for a specific request, overriding the default value.


3. Caching POST Requests

The postWithCache method allows you to cache the response of a POST request. This can be useful when the API always returns the same response for identical input data.

const response = await httpCache.postWithCache(
    url,
    { title, body, userId },  // request body
    {},                       // additional headers
    true,                     // cache the response
    { timeToLive: 30000 }     // cache options
);

4. Pattern-Based Invalidation

You can invalidate all cache entries whose key matches a given pattern. This is useful for refreshing a set of related resources.

const countExact   = await httpCache.invalidateCache('/posts/42');  // Invalidates an exact key
const countPattern = await httpCache.invalidateCache('/posts/*');   // Invalidates all keys starting with /posts/

5. Cache Statistics

Retrieve information about the current state of the cache (number of entries, size, etc.).

const stats = await httpCache.getStats();
console.log(stats);
// Example output:
// { size: 45, maxSize: 100, hits: 120, misses: 30, ... }

6. Full Cache Flush

To completely clear the cache (all entries), use:

await httpCache.clearHttpCache();

7. Response Metadata

Every object returned by getWithCache or postWithCache includes a _metadata property containing information about the origin of the data.

const data = await httpCache.getWithCache(url);
console.log(data._metadata);
// {
//   source: 'cache' | 'api',
//   cached: boolean,
//   createdAt: timestamp,
//   ttl: number
// }

8. Error Handling

The service encapsulates both network errors and cache errors. You can intercept them as shown in the controller example.


9. Usage with Dynamic Parameters

Build the URL with variable parameters (such as postId) to create unique cache keys per resource.

const url = `https://api.example.com/posts/${postId}`;

Complete example

export class UserController {
    private httpCache: IHttpCacheService;

    constructor() {
        this.httpCache = HttpCacheFactory.create('external-api', {
            storageType: 'disk',
            diskDir: 'src/core/cache/demo',
            defaultTTL: 10000,
            maxSize: 100
        });
    }

    public async getPostById(req: Request, res: Response) {
        const postId = req.params.id;
        const bypassCache = req.query.bypass === 'true'; // Allows bypassing the cache with ?bypass=true

        try {
            const url = `https://jsonplaceholder.typicode.com/posts/${postId}`;

            const cacheOptions = {
                bypassCache: bypassCache,
                timeToLive: 60000 // 1 minute (or use the default value)
            };

            console.log(`Requête pour le post ${postId}, bypass: ${bypassCache}`);

            const post = await this.httpCache.getWithCache(url, {}, cacheOptions);

            // Add information about the source (cache or API)
            const source = post?._metadata?.source || 'unknown';
            const cached = post?._metadata?.cached || false;

            res.json({
                success: true,
                data: post,
                source: source,
                cached: cached,
                timestamp: new Date().toISOString()
            });

        } catch (error: any) {
            console.error(`Erreur lors de la récupération du post ${postId}:`, error);
            res.status(500).json({
                success: false,
                error: error.message
            });
        }
    }

    // Method to retrieve comments for a post, using cache
    public async getCommentsByPostId(req: Request, res: Response) {
        const postId = req.params.id;
        const bypassCache = req.query.bypass === 'true';

        try {
            const url = `https://jsonplaceholder.typicode.com/posts/${postId}/comments`;

            const cacheOptions = {
                bypassCache: bypassCache,
                timeToLive: 300000 // 5 minutes
            };

            const comments: unknown = await this.httpCache.getWithCache(url, {}, cacheOptions);
            
            const source: any = comments?._metadata ? comments?._metadata.source : 'unknown';
            const cached: any = comments?._metadata ? comments?._metadata?.cached : false;

            res.json({
                success: true,
                data: comments,
                source: source,
                cached: cached,
                count: Array.isArray(comments) ? comments.length : 1,
                timestamp: new Date().toISOString()
            });

        } catch (error: any) {
            console.error(`Erreur pour les commentaires du post ${postId}:`, error);
            res.status(500).json({
                success: false,
                error: error.message
            });
        }
    }

    // Method for making a POST request with optional caching
    public async createPost(req: Request, res: Response) {
        const { title, body, userId, cacheResponse } = req.body;
        try {
            const url = 'https://jsonplaceholder.typicode.com/posts';
            const response = await this.httpCache.postWithCache(
                url,
                { title, body, userId },
                {},
                cacheResponse || true,
                { timeToLive: 30000 }
            );
            res.json({
                success: true,
                data: response,
                cached: cacheResponse || false,
                timestamp: new Date().toISOString()
            });
        } catch (error: any) {
            console.error('Erreur création de post:', error);
            res.status(500).json({
                success: false,
                error: error.message
            });
        }
    }

    // Method for obtaining HTTP cache statistics
    public async getCacheStats(req: Request, res: Response) {
        try {
            const stats = await this.httpCache.getStats();
            res.json(stats);
        } catch (error: any) {
            res.status(500).json({
                success: false,
                error: error.message
            });
        }
    }

    // Method to clear the HTTP cache
    public async clearHttpCache(req: Request, res: Response) {
        try {
            await this.httpCache.clearHttpCache();
            res.json({
                success: true,
                message: 'Cache HTTP vidé avec succès',
                timestamp: new Date().toISOString()
            });
        } catch (error: any) {
            res.status(500).json({
                success: false,
                error: error.message
            });
        }
    }

    // Method to invalidate a pattern in the HTTP cache
    public async invalidateCachePattern(req: Request, res: Response) {
        const pattern = req.query.pattern as string;
        if (!pattern) {
            return res.status(400).json({
                success: false,
                error: 'Le paramètre "pattern" est requis'
            });
        }

        try {
            const count = await this.httpCache.invalidateCache(pattern);
            res.json({
                success: true,
                message: `Cache invalidé pour le pattern: ${pattern}`,
                invalidatedCount: count,
                timestamp: new Date().toISOString()
            });
        } catch (error: any) {
            res.status(500).json({
                success: false,
                error: error.message
            });
        }
    }
} 

API Reference

Factory

HttpCacheFactory.create(appName?: string, config?)

Returns: IHttpCacheService

Core Methods

getWithCache

await cache.getWithCache("https://api.example.com/data");

postWithCache

await cache.postWithCache("https://api.example.com/data", payload, {}, true);

invalidateCache

await cache.invalidateCache("users:*");

clearHttpCache

await cache.clearHttpCache();

getStats

const stats = await cache.getStats();
console.log(stats.hitRate);

Testing

Run the example project by this command :
tsx example/src/bootstrap.ts

then run :

Method POST :
curl -X POST "http://localhost:4200/api/users/posts" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://jsonplaceholder.typicode.com/posts",
    "data": {
      "title": "test",
      "body": "content",
      "userId": 2
    },
    "cacheResponse": true
  }'
  
  
  Response : 
  {
      "success":true,
      "data": {
          "id": 101,
          "_metadata": {
              "cached": true,
              "timestamp": "2026-02-20T10:56:25.444Z",
              "url": "https://jsonplaceholder.typicode.com/posts",
              "method": "POST", 
              "responseTime":433
          }
      },
      "cached": true,
      "timestamp": "2026-02-20T10:56:25.444Z"
  }
  
  
  Method GET
  curl -X GET "http://localhost:4200/api/users/posts/1"
  
  Response :
  {
  "success": true,
  "data": {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
    "_metadata": {
      "cached": false,
      "timestamp": "2026-02-20T14:20:38.751Z",
      "url": "https://jsonplaceholder.typicode.com/posts/1",
      "method": "GET",
      "responseTime": 103,
      "cacheHit": false
    }
  },
  "source": "unknown",
  "cached": false,
  "timestamp": "2026-02-20T14:20:38.751Z"
}

Security

  • No automatic caching of sensitive headers
  • Namespace isolation supported
  • Manual invalidation available
  • Safe retry mechanism

License

MIT

Contributing

Contributions are welcome.

  1. Fork the repository
  2. Create a feature branch
  3. Submit a Pull Request

Built for scalable OptiCoreJs and Node.js applications.