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

@desolint/cache

v0.0.1

Published

Generic, fail-safe Redis cache wrapper (get/set/del/clear/has/getOrSet). No root export — import only what you need: @desolint/cache/config, /services.

Downloads

32

Readme

@desolint/cache

Generic, fail-safe Redis cache wrapper: get, set, del, clear, has, getOrSet.

No root export. import ... from '@desolint/cache' resolves to nothing — import only what you need:

@desolint/cache/config     @desolint/cache/services

| Subpath | What it's for | | ----------- | ------------------------------------------------------------------------------ | | /config | Open the Redis connection. Nothing else. | | /services | The actual cache operations — get, set, del, clear, has, getOrSet. |


Requirements

  • Node.js 22 or newer (declared in engines)
  • npm 7 or newer — npm 7+ installs peer dependencies automatically

Install

npm install @desolint/cache

That is all you need on npm 7+: ioredis is listed as a peer dependency, so npm resolves and installs it for you.

Neither installs peer dependencies automatically, so name it explicitly:

yarn add @desolint/cache ioredis
# or
pnpm add @desolint/cache ioredis

Why ioredis is a peer dependency, not a regular one

ioredis owns a connection pool to Redis. If this package carried its own copy, your application and this cache would open two independent sets of connections to the same Redis server — doubling connections, and making it impossible to share configuration, event handlers or a mock between them.

Declaring it as a peer dependency means npm reuses the copy your application already has instead of nesting a second one under this package. You keep control of the version; this package just states the range it works with.


Quick start

// config/cache.js
import {initializeCache} from '@desolint/cache/config';

initializeCache({
  redisUrl: process.env.REDIS_URL,
  defaultTtlSeconds: 300,
  onConnection: () => console.log('Cache connected'),
  onError: (error) => console.error('Cache error', {error}),
});
// services/permissionsServices.js
import {getOrSet, del} from '@desolint/cache/services';

const permissions = await getOrSet({
  key: `permissions:${organizationId}:${userId}`,
  ttlSeconds: 300,
  fn: async () => await fetchPermissionsFromDb({userId, organizationId}),
});

// invalidate on write
await del({key: `permissions:${organizationId}:${userId}`});

/config

initializeCache({redisUrl, defaultTtlSeconds?, options?, onConnection?, onError?})

Connects a dedicated ioredis instance and returns it — deliberately separate from any Redis connection you already run for something else (BullMQ, pub/sub, ...). A cache connection needs different resilience tuning than a queue connection: fail-fast (enableOfflineQueue: false, short commandTimeout) so a slow/unreachable Redis can never hang a request, versus BullMQ's maxRetriesPerRequest: null. Don't share one connection between the two.

| Param | Type | Required | Notes | | ------------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | redisUrl | string | yes | Redis connection URI. | | defaultTtlSeconds | number | no | Fallback used by set/getOrSet when a call omits its own ttlSeconds. See Permanent keys below. | | options | Partial<RedisOptions> | no | Merged over the built-in fail-fast defaults (maxRetriesPerRequest: 1, enableOfflineQueue: false, commandTimeout: 1000) — override any of them. | | onConnection | () => void | no | Fires on the connection's 'connect' event. | | onError | (error: Error) => void | no | Fires for both connection-level Redis errors and operation-level errors that /services swallows instead of throwing (see below) — one hook covers both. |

Returns: the ioredis instance itself, if you need it directly.


/services

Every function here is fail-safe: none of them throw. get/has return null/false on any error — indistinguishable from a genuine cache miss, by design, so the caller always has a safe path (fall through to the real data source). set/del/clear swallow their errors too, reporting them through whichever onError you gave initializeCache, if any. A down/unreachable Redis degrades your app to "always compute fresh," never to "the request fails."

| Function | Signature | Returns | What it does | | ---------- | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | get | {key} | T \| null | Reads and JSON-parses a key. null on a miss or any error. | | set | {key, value, ttlSeconds?} | void | JSON-serializes and writes a key. See Permanent keys below for what happens when ttlSeconds is omitted. | | del | {key} | void | Removes one key. | | has | {key} | boolean | Whether a key exists. false on any error. | | clear | {pattern} | void | Deletes every key matching a pattern (e.g. permissions:*) via SCAN — never KEYS, which blocks the whole Redis instance on a large keyspace. | | getOrSet | {key, ttlSeconds?, fn} | T | Cache-aside in one call: get → miss → fn()set → return. On a cache outage, still calls fn() and returns its result — it just won't have anywhere to cache it. |

Permanent keys

set/getOrSet resolve ttlSeconds as: the call's own ttlSeconds, else initializeCache's defaultTtlSeconds, else no TTL at all — the key is written with no EX flag and lives forever until explicitly del'd or clear'd. This is a deliberate, supported way to cache something permanently (e.g. a lookup table that only changes on deploy) — not an oversight, and nothing enforces a TTL at any level. If you want every key in your app to expire by default, set defaultTtlSeconds yourself.


Development

npm install     # install dependencies
npm run build   # type-check, then bundle each subpath into dist/
npm test        # jest
npm run lint    # eslint

scripts/build.mjs bundles each subpath into one self-contained JS file plus a .d.ts, then deletes everything else from dist/ — internal modules (src/redis/*, src/shared/*) never ship, so there is nothing for an editor or a moduleResolution: "node" consumer to resolve beyond the two public subpaths documented above.


License

MIT © Desolint — see LICENSE.

Free to use, modify and redistribute, commercially or otherwise. Provided "as is", without warranty or liability of any kind.