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

cachora

v0.1.0

Published

A type-safe, resilient and extensible cache toolkit for Node.js and NestJS

Readme

Cachora

A strict, type-safe cache toolkit for Node.js and NestJS with pluggable stores, local/distributed locking, stale-while-revalidate, tag invalidation, bulk operations and multi-tier caching.

See the complete usage guide for installation, configuration, feature examples, API behavior, NestJS integration, and production guidance.

Requirements

  • Node.js 20 or newer
  • TypeScript 5.5 or newer when consuming TypeScript declarations

Architecture

createCache() is the framework-neutral composition root. The public Cache facade delegates to focused read, write, computation, entry-policy, observability, and maintenance services. Store contracts are split by capability (ReadableStore, WritableStore, DeletableStore, bulk contracts, lifecycle, clear, and tag generations), while the original composite Store and BulkStore contracts remain available.

Applications extend Cachora by supplying abstractions rather than changing core code: Store, Serializer, LockProvider, Telemetry, BackgroundTaskRunner, TierFailureReporter, and the NestJS CacheFactory token are explicit extension points.

Install

npm install cachora

Install only the optional integration you use:

npm install redis
npm install @aws-sdk/client-s3
npm install @nestjs/common @nestjs/core reflect-metadata rxjs

Node.js

import {
  createCache,
  MemoryStore,
} from 'cachora';

const cache = createCache({
  store: new MemoryStore({
    maxEntries: 10_000,
    maxSizeBytes: 64 * 1024 * 1024,
  }),
  keyspace: {
    namespace: 'invoice-api',
    version: 1,
  },
  defaults: {
    ttlMs: 60_000,
    staleTtlMs: 30_000,
    jitterRatio: 0.1,
  },
});

const invoice = await cache.getOrCompute(
  `invoice:${invoiceId}`,
  ({ signal }) => invoiceRepository.find(invoiceId, { signal }),
  {
    tags: ['invoices', `invoice:${invoiceId}`],
    lock: 'local',
  },
);

await cache.invalidateTag(`invoice:${invoiceId}`);
await cache.close();

Falsy values including undefined, null, false, 0 and empty strings are valid cached values. Use getWithMetadata() when the application must distinguish a cached undefined from a cache miss.

Expiration and refresh

const value = await cache.getOrCompute('expensive-report', buildReport, {
  ttlMs: 60_000,
  staleTtlMs: 30_000,
  staleIfErrorMs: 5 * 60_000,
  earlyRefresh: {
    beta: 1,
    minimumRemainingTtlMs: 5_000,
  },
  refresh: 'background',
});

Early refresh uses stored factory computation time and deterministic injectable clock/random sources. staleIfErrorMs extends physical retention, but an expired value is returned only after refresh fails and only inside that window. Background refresh requires an explicit BackgroundTaskRunner; without one, refresh remains blocking so serverless runtimes do not lose untracked work.

Redis

The Redis adapter accepts a structural client interface, so the library core does not own or configure application infrastructure.

import { createClient } from 'redis';
import { createCache } from 'cachora';
import {
  RedisLockProvider,
  RedisStore,
} from 'cachora/redis';

const client = createClient({ url: process.env.REDIS_URL });
await client.connect();

const cache = createCache({
  store: new RedisStore({
    client,
    keyPrefix: 'billing',
    ownsClient: false,
  }),
  lock: new RedisLockProvider({ client }),
  keyspace: { namespace: 'invoice-api', version: 2 },
  defaults: {
    lock: 'local+distributed',
    ttlMs: 60_000,
  },
});

When ownsClient is false, cache.close() does not close the supplied Redis client.

Multi-tier

import {
  createCache,
  MemoryStore,
  TieredStore,
} from 'cachora';
import { RedisStore } from 'cachora/redis';

const store = new TieredStore({
  tiers: [
    {
      store: new MemoryStore({ maxEntries: 5_000 }),
      ttlCapMs: 30_000,
    },
    {
      store: new RedisStore({ client: redis }),
      ttlCapMs: 15 * 60_000,
    },
  ],
  promotion: 'best-effort',
  write: 'all-settled',
});

const cache = createCache({
  store,
  keyspace: { namespace: 'invoice-api' },
});

Reads start at L1. Values found in a lower tier are promoted upward without extending their remaining lifetime. Best-effort failures are published to the cachora:tiered-store:error Node diagnostics channel by default. Supply a TierFailureReporter to integrate another observability strategy. When tests or the application use a custom Clock, pass the same instance to TieredStore and createCache.

File and S3

import { FileStore } from 'cachora/file';
import { S3Store } from 'cachora/s3';

const fileStore = new FileStore({
  directory: '/var/tmp/my-application-cache',
});

const s3Store = new S3Store({
  client: s3,
  bucket: 'application-cache',
  prefix: 'production',
});

The file adapter hashes keys and writes atomically. The S3 adapter never creates buckets or modifies bucket lifecycle configuration.

NestJS

Synchronous configuration

import { Module } from '@nestjs/common';
import { CacheModule } from 'cachora/nestjs';
import { MemoryStore } from 'cachora';

@Module({
  imports: [
    CacheModule.forRoot({
      isGlobal: true,
      cacheOptions: {
        store: new MemoryStore({ maxEntries: 10_000 }),
        keyspace: { namespace: 'invoice-api' },
        defaults: { ttlMs: 60_000 },
      },
    }),
  ],
})
export class AppModule {}

Asynchronous configuration

CacheModule.forRootAsync({
  isGlobal: true,
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    cacheOptions: {
      store: new RedisStore({
        client: config.getOrThrow('REDIS_CLIENT'),
      }),
      keyspace: {
        namespace: config.getOrThrow('SERVICE_NAME'),
      },
    },
  }),
});

Injection

import { Injectable } from '@nestjs/common';
import { InjectCache } from 'cachora/nestjs';
import type { Cache } from 'cachora';

@Injectable()
export class InvoiceService {
  constructor(@InjectCache() private readonly cache: Cache) {}

  find(invoiceId: string) {
    return this.cache.getOrCompute(
      `invoice:${invoiceId}`,
      () => this.loadInvoice(invoiceId),
      { tags: [`invoice:${invoiceId}`] },
    );
  }
}

The Nest lifecycle provider closes resources during application shutdown.

Custom cache factory

CacheModule depends on CACHE_FACTORY, so applications can replace cache construction with an injectable Strategy. In asynchronous registration, put the custom provider in extraProviders:

import { Injectable } from '@nestjs/common';
import {
  createCache,
  type Cache,
  type CreateCacheOptions,
} from 'cachora';
import {
  CACHE_FACTORY,
  CacheModule,
  type CacheFactory,
} from 'cachora/nestjs';

@Injectable()
class ApplicationCacheFactory implements CacheFactory {
  constructor(private readonly telemetry: ApplicationTelemetry) {}

  create(options: CreateCacheOptions): Cache {
    return createCache({ ...options, telemetry: this.telemetry });
  }
}

CacheModule.forRootAsync({
  useFactory: () => ({ cacheOptions }),
  extraProviders: [
    ApplicationTelemetry,
    { provide: CACHE_FACTORY, useClass: ApplicationCacheFactory },
  ],
});

Typed key schema

interface ApplicationCache {
  'user-profile': {
    params: { userId: string };
    value: UserProfile;
  };
  'invoice-summary': {
    params: { invoiceId: number };
    value: InvoiceSummary | null;
  };
}

const typedCache = createTypedCache<ApplicationCache>({
  cache,
  keys: {
    'user-profile': ({ userId }) => `user:${userId}:profile`,
    'invoice-summary': ({ invoiceId }) => `invoice:${invoiceId}:summary`,
  },
});

The key parameters, factory result and returned value are checked together at compile time.

Failure semantics

Default behavior favors application availability:

  • read infrastructure failure becomes a miss and emits an error event
  • write failure is reported but does not discard a valid factory result
  • deletion/invalidation failure is thrown
  • corrupt values are reported as misses

Override these choices through failure in createCache.

All Cachora-originated failures extend CacheError and expose stable code, operation, store, retryable, and cause metadata. Caller cancellation is never converted into an availability fallback: an aborted operation always propagates its AbortSignal.reason.

Testing consumers

cachora/testing exports FakeClock, FixedRandom, and CollectingTelemetry. NestJS integration can be isolated with Test.createTestingModule() by mocking CACHE or overriding CACHE_FACTORY.

Public subpath exports

  • cachora
  • cachora/memory
  • cachora/file
  • cachora/redis
  • cachora/s3
  • cachora/nestjs
  • cachora/testing

Compatibility

The Store contracts, Node.js entrypoint, and NestJS forRoot / forRootAsync registrations remain available. Method names were intentionally modernized before the stable release: use getWithMetadata, getOrCompute, getAndDelete, exists, and wrapFunction. Invalid configuration now fails earlier with typed errors, lifecycle failures are no longer silently ignored, and caller cancellation always propagates. See CHANGELOG.md for the complete migration summary.