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

keyv-cache-proxy

v0.2.0

Published

A transparent caching proxy for any object using Keyv - automatically cache method calls with TTL support

Readme

keyv-cache-proxy

A transparent caching proxy for any object using Keyv - automatically cache method calls with TTL support.

Features

  • 🚀 Zero-config caching: Wrap any object to automatically cache all method calls
  • ⏱️ TTL support: Set time-to-live for cached values
  • 🔑 Flexible storage: Use any Keyv-compatible storage adapter
  • 🎯 Deep proxy: Automatically handles nested objects
  • 📊 Cache observability: Optional hooks for monitoring and modifying cached/fetched data
  • 🔄 Async-first: Automatically converts all methods to async

Installation

bun add keyv-cache-proxy keyv

Or with npm/yarn/pnpm:

npm install keyv-cache-proxy keyv

Quick Start

import { KeyvCacheProxy } from 'keyv-cache-proxy';
import Keyv from 'keyv';

// Create a Keyv instance with any storage backend
const store = new Keyv();

// Wrap any object with caching
const cachedAPI = KeyvCacheProxy({
  store,
})(yourAPIClient);

// All method calls are now automatically cached!
const result = await cachedAPI.fetchData('param1', 'param2');

Usage Examples

Basic Usage

import { KeyvCacheProxy } from 'keyv-cache-proxy';
import Keyv from 'keyv';

const myObject = {
  expensiveOperation(a: number, b: number) {
    console.log('Computing...');
    return a + b;
  }
};

const cached = KeyvCacheProxy({
  store: new Keyv(),
  ttl: 60000, // 1 minute
})(myObject);

// First call: executes the method
await cached.expensiveOperation(1, 2); // Logs: "Computing..."

// Second call: returns cached result
await cached.expensiveOperation(1, 2); // No log, returns from cache

With GitHub API (Octokit)

import { Octokit } from 'octokit';
import { KeyvCacheProxy } from 'keyv-cache-proxy';
import Keyv from 'keyv';
import KeyvNedbStore from 'keyv-nedb-store';

// Use persistent storage
const kv = new Keyv(new KeyvNedbStore('.cache/github.yaml'));

const gh = KeyvCacheProxy({
  store: kv,
  ttl: 600000, // 10 minutes
  prefix: 'github.',
  onCached: (key, value) => console.log('Cache hit:', key),
  onFetched: (key, value) => console.log('Fetched fresh:', key),
})(new Octokit().rest);

// API calls are now cached
const repo = await gh.repos.get({ owner: 'snomiao', repo: 'keyv-cache-proxy' });

With Notion API

import { Client } from '@notionhq/client';
import { KeyvCacheProxy } from 'keyv-cache-proxy';
import Keyv from 'keyv';

const notion = new Client({ auth: process.env.NOTION_API_KEY });

// Cache Notion API calls to reduce rate limiting
const cachedNotion = KeyvCacheProxy({
  store: new Keyv(),
  ttl: 300000, // 5 minutes
  prefix: 'notion.',
})(notion);

// These calls will be cached
const database = await cachedNotion.databases.query({
  database_id: 'your-database-id',
});

const page = await cachedNotion.pages.retrieve({
  page_id: 'your-page-id',
});

With Slack API

import { WebClient } from '@slack/web-api';
import { KeyvCacheProxy } from 'keyv-cache-proxy';
import Keyv from 'keyv';

const slack = new WebClient(process.env.SLACK_TOKEN);

// Cache Slack API calls
const cachedSlack = KeyvCacheProxy({
  store: new Keyv(),
  ttl: 600000, // 10 minutes
  prefix: 'slack.',
})(slack);

// Cached API calls
const channels = await cachedSlack.conversations.list();
const userInfo = await cachedSlack.users.info({ user: 'U123456' });
const messages = await cachedSlack.conversations.history({
  channel: 'C123456',
});

With Custom Storage Backends

import { KeyvCacheProxy } from 'keyv-cache-proxy';
import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';
import KeyvMongo from '@keyv/mongo';

// Redis
const redisCache = KeyvCacheProxy({
  store: new Keyv(new KeyvRedis('redis://localhost:6379')),
  ttl: 3600000,
})(yourObject);

// MongoDB
const mongoCache = KeyvCacheProxy({
  store: new Keyv(new KeyvMongo('mongodb://localhost:27017')),
  ttl: 3600000,
})(yourObject);

// SQLite (via keyv-sqlite)
const sqliteCache = KeyvCacheProxy({
  store: new Keyv('sqlite://cache.db'),
  ttl: 3600000,
})(yourObject);

Cache Observability & Data Modification

Track cache performance:

let hits = 0;
let fetches = 0;

const cached = KeyvCacheProxy({
  store: new Keyv(),
  ttl: 60000,
  onCached: (key, value) => {
    hits++;
    console.log(`Cache hit for ${key}. Total hits: ${hits}`);
  },
  onFetched: (key, value) => {
    fetches++;
    console.log(`Fetched fresh for ${key}. Total fetches: ${fetches}`);
  },
})(myObject);

Modify cached/fetched data:

const cached = KeyvCacheProxy({
  store: new Keyv(),
  ttl: 60000,
  // Add metadata to cached data (called on every invocation)
  onCached: (key, value) => {
    if (value !== undefined) {
      console.log('Returning cached data');
      return { data: { ...value, fromCache: true, cachedAt: Date.now() } };
    }
  },
  // Transform fetched data before caching
  onFetched: (key, value) => {
    console.log('Processing fresh data');
    return { data: { ...value, fetchedAt: Date.now(), processed: true } };
  },
})(myObject);

Force cache refresh:

const cached = KeyvCacheProxy({
  store: new Keyv(),
  ttl: 60000,
  onCached: (key, value) => {
    // Return { skip: true } to force refetch even if cached
    if (value && isStale(value)) {
      return { skip: true }; // Forces cache miss and refetch
    }
    // Return undefined to use cached value
  },
})(myObject);

Custom TTL per request:

const cached = KeyvCacheProxy({
  store: new Keyv(),
  ttl: 60000, // Default 1 minute
  onFetched: (key, value) => {
    // Cache user data longer than other data
    if (key.includes('user')) {
      return { data: value, ttl: 3600000 }; // 1 hour
    }
    return { data: value }; // Use default TTL
  },
})(myObject);

API

KeyvCacheProxy(options)

Creates a cache proxy factory function.

Options

  • store (required): A Keyv instance for cache storage
  • ttl (optional): Time-to-live for cached entries in milliseconds (can be overridden per request via onFetched)
  • prefix (optional): Prefix for cache keys (default: "")
  • onCached (optional): Hook called on every invocation. Receives key and cached value (or undefined on cache miss).
    • Return undefined → Use original cached value
    • Return { skip: true } → Treat as cache miss and refetch
    • Return { data: <value> } → Return modified cached value
    • Signature: (key: string, value: any) => { data?: any } | { skip: true } | undefined | Promise<...>
  • onFetched (optional): Hook called when data is freshly fetched (cache miss). Receives key and fetched value.
    • Return undefined → Cache original fetched value with default TTL
    • Return {} → Cache original fetched value with default TTL (same as undefined)
    • Return { data: <value> } → Cache modified value
    • Return { data: <value>, ttl: <ms> } → Cache modified value with custom TTL
    • Return { skip: true } → Skip caching but still return the fetched value
    • Signature: (key: string, value: any) => { data?: any, ttl?: number } | { skip: true } | undefined | Promise<...>

Returns

A function that takes an object and returns a proxied version with automatic caching.

Cache Key Generation

Cache keys are generated based on:

  • Method name
  • Arguments (JSON stringified)
  • Prefix (if provided)

Format: ${prefix}${methodName}:${JSON.stringify(args)}

Type Safety

The proxy preserves TypeScript types and automatically converts all methods to async:

type DeepAsyncMethod<T> = {
  [K in keyof T]: T[K] extends (...args: infer A) => infer R
    ? (...args: A) => Promise<Awaited<R>>
    : T[K] extends object
    ? DeepAsyncMethod<T[K]>
    : T[K];
};

How It Works

The KeyvCacheProxy uses JavaScript Proxy to intercept method calls:

  1. When a method is called, it generates a cache key from the method name and arguments
  2. Checks the Keyv store for an existing cached result
  3. If found (cache hit), returns the cached value
  4. If not found (cache miss), executes the original method
  5. Stores the result in the cache with the specified TTL
  6. Returns the result

Nested objects are automatically wrapped with the same caching behavior.

Storage Backends

You can use any Keyv-compatible storage adapter:

License

MIT © snomiao

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

  • keyv - Simple key-value storage with support for multiple backends
  • keyv-nedb-store - NeDB storage adapter for Keyv