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

@clov-std/elysia-cache

v1.0.9

Published

Response caching for Elysia as a route macro, stored in any @clov-std/kv-store adapter.

Readme

Clov Elysia Cache

Response caching for Elysia, as a macro. Put isCached: { ttl: 60 } on a route, guard or group, and repeated requests are answered from the store in beforeHandle, before your handler runs.

Storage goes through @clov-std/kv-store: memory by default, Redis when you need it, same routes either way.

bun add @clov-std/elysia-cache elysia typebox

elysia (v2) and typebox are peer dependencies.

Usage

Basic

ttl is in seconds.

import { cachePlugin } from '@clov-std/elysia-cache';
import { Elysia } from 'elysia';

new Elysia()
	.use(cachePlugin())
	.get(
		'/catalog/items',
		{ isCached: { ttl: 60 } }, // cached for 60 seconds
		() => listItems()
	)
	.listen(3000);

Redis

Pass a BunRedisStore (or any KvStore adapter) to keep the cache across restarts and share it between instances.

import { cachePlugin } from '@clov-std/elysia-cache';
import { BunRedisStore } from '@clov-std/kv-store';
import { Elysia } from 'elysia';

const store = new BunRedisStore('redis://localhost:6379');

new Elysia()
	.use(cachePlugin(store))
	.get('/catalog/items', { isCached: { ttl: 60 } }, () => listItems())
	.listen(3000);

Elysia keeps one plugin instance per name, so the store has to go to the first cachePlugin() in the app tree. A later cachePlugin(otherStore) on the same app is ignored.

vary

By default the key only includes the authorization header. If the response depends on other headers, list them:

new Elysia()
	.use(cachePlugin())
	.get(
		'/catalog/items',
		{ isCached: { ttl: 60, vary: ['authorization', 'accept-language'] } },
		() => listItems()
	)
	.listen(3000);

Custom key

keyGenerator receives the Request and the parsed body. Return a string, or undefined to skip caching for that request (a promise of either works too). When it's set, vary is ignored.

import { cachePlugin, generateCacheKey } from '@clov-std/elysia-cache';
import { Elysia } from 'elysia';

new Elysia()
	.use(cachePlugin())
	.get(
		'/catalog/items',
		{
			isCached: {
				ttl: 60,
				keyGenerator: (request, body) =>
					generateCacheKey(request, body, ['accept-language'])
			}
		},
		() => listItems()
	)
	.listen(3000);

generateCacheKey is exported so you can keep the default format with your own headers.

How it behaves

Keys are readable strings: method:url, the vary header values, then the parsed body. Keys over 1024 characters are hashed with SHA-256.

On a hit, the stored response is returned from beforeHandle with X-Cache: HIT and Cache-Control: max-age=<remaining>, private, where the remaining time is computed from when the entry was created.

On a miss, the handler runs, the response is stored for ttl seconds, and it goes out with X-Cache: MISS and Cache-Control: max-age=<ttl>, private.

A few things are never cached:

  • undefined return values.
  • Requests whose parsed body is or contains binary data (Blob, ArrayBuffer, typed arrays).

Response instances are cloned before being stored or replayed, so the same cached body can be served many times.

API

cachePlugin(store?)

Creates the plugin. store defaults to a new MemoryStore.

generateCacheKey(request, body?, vary?)

Builds the key for a request. Without vary, every request header is included. Returns undefined when the body is binary and the request must not be cached.

CacheOptions

| Option | Type | Default | Description | | -------------- | ------------------------------------------------------------------------------------------ | --------------------------------------- | -------------------------------------------------------------------------- | | ttl | number | - | Time to live, in seconds. | | vary | string[] | ['authorization'] | Header names included in the key. | | keyGenerator | (request: Request, body: unknown) => string \| undefined \| Promise<string \| undefined> | generateCacheKey(request, body, vary) | Custom key builder. Returning undefined opts the request out of caching. |

Docs

API reference: clovlabs.github.io/std

License

MIT, see LICENSE.md.