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

@concepta/nestjs-cache

v8.0.0-alpha.12

Published

Rockets NestJS Cache

Downloads

636

Readme

@concepta/nestjs-cache

Database-backed cache module for NestJS using DDD/CQRS. Provides typed cache entries keyed by key, type, and assigneeId with optional TTL expiration.

Project

NPM Latest NPM Downloads GH Last Commit GH Contrib NestJS Dep

Table of Contents

Installation

yarn add @concepta/nestjs-cache @nestjs/common @nestjs/config @nestjs/core

This package is ESM-only and targets NestJS 12 on Node >= 22.12.

Dependencies

zod is a direct dependency — request/response shapes are Zod v4 (Standard Schema) schemas.

Peer Dependencies

| Package | Required | Notes | | --- | --- | --- | | @nestjs/common | Yes | NestJS core — install explicitly, no longer bundled | | @nestjs/config | Yes | Module option registration | | @nestjs/core | Yes | Module reference and reflection | | @nestjs/cqrs | No | Optional peer — required in practice for CommandBus/QueryBus/EventBus | | typeorm | No | Only if using the TypeORM repository adapter | | @concepta/nestjs-crud | No | Only if using the HTTP gateway layer | | @concepta/typeorm-seeding | No | Only for database seeding | | @faker-js/faker | No | Only for database seeding |

Module Registration

Synchronous

import { CacheModule } from '@concepta/nestjs-cache';

@Module({
  imports: [
    CacheModule.register({
      settings: {
        expiresIn: '1h',
      },
    }),
  ],
})
export class AppModule {}

Asynchronous

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: async () => ({
        settings: {
          expiresIn: '1h',
        },
      }),
    }),
  ],
})
export class AppModule {}

register() / registerAsync() register the module locally (scoped to the importing module).

forRoot() / forRootAsync() register the module globally. This is required when using forFeature() in other modules, since forFeature() injects tokens exported by the core module.

Multi-Tenancy with forFeature

Use forFeature() to register dynamic CacheRepository providers for each entity key. This allows different parts of your application to maintain separate cache tables.

@Module({
  imports: [
    CacheModule.forFeature(['userCache', 'sessionCache']),
  ],
})
export class UserModule {}

Each entity key maps to a CacheRepository instance resolved at runtime by CacheRepositoryResolver.

Options

forRoot() and registerAsync() accept CacheOptionsInterface merged with CacheExtrasInterface (extras are passed to setExtras on the ConfigurableModuleBuilder):

interface CacheExtrasInterface {
  global?: boolean;
  providers?: Provider[];
  repositories?: {
    cache?: Type<CacheRepositoryInterface>;
  };
}

interface CacheOptionsInterface {
  settings?: CacheSettingsInterface;
}

interface CacheSettingsInterface {
  expiresIn?: string | null;
}

The expiresIn value accepts time span strings (e.g. '60', '2 days', '10h', '7d'). When not provided, entries do not expire.

forFeature() accepts an array of entity key strings. Each key creates a dynamic CacheRepository provider:

CacheModule.forFeature(entityKeys: string[])

Pass repositories.cache to override the default CacheRepository with a custom implementation.

Architecture Overview

The module follows a DDD/CQRS architecture with four layers:

Gateway (HTTP)
  |
Application (Commands / Queries)
  |
Domain (Cache aggregate, Events)
  |
Infrastructure (Repository, Mapper, Schemas, Config)
  • Domain -- Cache aggregate extending DomainAggregate<CacheInterface>, domain events
  • Application -- 7 commands and 3 queries dispatched via @nestjs/cqrs
  • Infrastructure -- CacheRepository with ctx-first signatures, CacheMapper for entity-to-aggregate conversion (DI-injected), CacheRepositoryResolver for multi-tenancy, Zod schemas
  • Gateway -- HTTP request handlers bridging @concepta/nestjs-crud to domain commands

App Context

Commands, queries, and repository methods accept a PlainLiteralObject as their ctx argument. This context is threaded through the transaction scope and repository layer automatically. In HTTP contexts the gateway provides the context; for programmatic use, pass any plain object:

const cache = await this.commandBus.execute<CreateCacheCommand, Cache>(
  new CreateCacheCommand({}, 'userCache', dto),
);

Context Overlay

The cache module uses a context overlay to resolve the entity namespace for each HTTP request. This is required when using the CRUD gateway.

CacheNamespace Decorator

Apply @CacheNamespace({ name }) to a controller (or via extraDecorators on a generated CRUD controller) to associate it with a cache entity key:

import { CacheNamespace } from '@concepta/nestjs-cache';

// For generated CRUD controllers, pass via extraDecorators:
CrudModule.forFeature<CacheInterface>({
  crud: {
    controller: {
      entity: 'userCache',
      path: 'cache/user',
      extraDecorators: [CacheNamespace({ name: 'userCache' })],
      // ...
    },
  },
})

How It Works

  1. CacheContextOverlay reads @CacheNamespace metadata via Reflector
  2. CacheContextOverlay extends ContextOverlayInterceptor and is registered as a global APP_INTERCEPTOR. Its attach() method resolves the namespace and calls ctx.defineOverlay(CacheCtx, resolved)
  3. Gateway request handlers use @Ctx(CacheCtx) (or ctx.with(CacheCtx)) to get { namespace }, used as the entity key for repository resolution

Commands

| Command | Description | | --- | --- | | CreateCacheCommand | (ctx, namespace, dto) -- Create a new cache entry | | UpdateCacheCommand | (ctx, namespace, id, dto) -- Partial update (data and expiresIn) | | ReplaceCacheCommand | (ctx, namespace, id, dto) -- Full replacement (creates if ID not found) | | UpsertCacheCommand | (ctx, namespace, dto) -- Create or update by key/type/assigneeId | | RemoveCacheCommand | (ctx, namespace, id) -- Hard delete by ID | | ArchiveCacheCommand | (ctx, namespace, id) -- Soft delete by ID | | ClearCachesByAssigneeCommand | (ctx, namespace, assigneeId) -- Remove all entries for an assignee |

Dispatching a Command

import { CommandBus } from '@nestjs/cqrs';
import { CreateCacheCommand, Cache } from '@concepta/nestjs-cache';

const cache = await this.commandBus.execute<CreateCacheCommand, Cache>(
  new CreateCacheCommand(ctx, 'userCache', {
    key: 'dashboard-filter',
    type: 'user-preference',
    assigneeId: userId,
    data: JSON.stringify(filterState),
    expiresIn: '7d',
  }),
);

Queries

| Query | Description | | --- | --- | | GetCacheQuery | (ctx, namespace, id) -- Get by ID (throws CacheNotFoundException) | | FindOneCacheQuery | (ctx, namespace, key, type, assigneeId) -- Find by key/type/assigneeId (returns null) | | FindCachesByAssigneeQuery | (ctx, namespace, assigneeId) -- Find all entries for an assigneeId |

Dispatching a Query

import { QueryBus } from '@nestjs/cqrs';
import { FindOneCacheQuery, Cache } from '@concepta/nestjs-cache';

const cache = await this.queryBus.execute<FindOneCacheQuery, Cache | null>(
  new FindOneCacheQuery(ctx, 'userCache', 'dashboard-filter', 'user-preference', userId),
);

Domain Events

All events carry an eventContext and a plain CacheInterface snapshot.

| Event | Emitted When | | --- | --- | | CacheCreatedEvent | New cache entry created | | CacheUpdatedEvent | Cache data updated | | CacheReplacedEvent | Cache fully replaced | | CacheExtendedEvent | Cache expiration extended |

Handling an Event

import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { CacheCreatedEvent } from '@concepta/nestjs-cache';

@EventsHandler(CacheCreatedEvent)
export class CacheCreatedListener implements IEventHandler<CacheCreatedEvent> {
  handle(event: CacheCreatedEvent): void {
    const { eventContext, cache } = event;
    // react to cache creation
  }
}

Cache Aggregate

The Cache class extends DomainAggregate<CacheInterface> and encapsulates all cache domain logic.

Factory Methods

// Create with auto-generated UUID
const cache = Cache.create(eventContext, dto, expirationDate);

// Create with a specific ID
const cache = Cache.createWithId(eventContext, id, dto, expirationDate);

Reconstitution from a database entity is handled by CacheMapper (see Repository).

Operations

// Replace all fields (preserves id and dateCreated)
cache.replace(eventContext, dto, expirationDate);

// Update only the data field
cache.updateData(eventContext, newData);

// Extend expiration
cache.extend(eventContext, expirationDate);

// Convert to plain CacheInterface object (inherited from DomainAggregate)
const plain = cache.toPlain();

Expiration Policy

CacheExpirationPolicy (exported with its CacheExpirationSettings interface) converts expiresIn time spans into concrete expiration dates. It is provided in DI from the module settings, so the module's default expiresIn is used when a request does not supply one.

interface CacheExpirationSettings {
  expiresIn?: string | null;
}

class CacheExpirationPolicy {
  constructor(settings?: CacheExpirationSettings);
  get defaultExpiresIn(): string | null;
  resolveExpirationDate(expiresIn?: string | null): Date | null;
}

resolveExpirationDate() resolves the given time span (falling back to the default) into a Date, or null when no expiration applies. An invalid time span throws CacheInvalidExpiredDateException.

Repository

CacheRepository uses a ctx-first calling convention for multi-tenancy support. All methods take PlainLiteralObject as the first argument.

The repository receives a DI-injected CacheMapper that converts database entities to Cache aggregates via toDomain() and aggregates back to persistence form via toPersistence().

| Method | Signature | | --- | --- | | get | (ctx, id) => Promise<Cache \| null> | | findOne | (ctx, { key, type, assigneeId }) => Promise<Cache \| null> | | findAllByAssignee | (ctx, assigneeId) => Promise<Cache[]> | | save | (ctx, cache) => Promise<void> | | remove | (ctx, cache) => Promise<void> | | removeAllByAssignee | (ctx, assigneeId) => Promise<void> | | softRemove | (ctx, cache) => Promise<void> |

Repository Resolution

const cacheRepo = this.repositoryResolver.resolve(ctx.entity);
const cache = await cacheRepo.get(ctx, id);

CacheRepositoryResolver looks up the repository by entity key. Entity keys are registered via CacheModule.forFeature().

Schemas

Request and response shapes are Zod v4 (Standard Schema) schemas.

| Schema | Entry Point | Fields | | --- | --- | --- | | cacheCreateSchema | main | key, type, assigneeId, data (optional, nullable), expiresIn (optional, nullable) | | cacheUpdateSchema | main | data (optional, nullable), expiresIn (optional, nullable) | | cacheSchema | main | Full entity response (key, type, data, assigneeId, expirationDate, + common entity fields) | | cachePaginatedSchema | optional/crud | Paginated envelope wrapping cacheSchema |

The expiresIn field accepts time span strings: '60', '2 days', '10h', '7d'.

expiresIn is request-only: the response schema cacheSchema intentionally omits it. The domain converts expiresIn into the computed expirationDate, which is what persisted entities and responses carry.

Exceptions

| Exception | Description | | --- | --- | | CacheNotFoundException | Cache ID not found (HTTP 404) | | CacheEntityNotFoundException | Entity key not registered via forFeature() | | CacheInvalidExpiredDateException | Invalid expiresIn format (HTTP 400) | | CacheException | Base cache exception |

HTTP Controller with CRUD Module

Use @concepta/nestjs-crud to expose cache operations as REST endpoints. The gateway request/handler classes are exported from @concepta/nestjs-cache/optional/crud.

The CRUD gateway needs the surrounding modules registered as well: CqrsModule.forRoot(), CrudModule.forRoot() (with CrudCqrsResolver as the default resolver), CoreModule.forRoot() (context overlays), and a RepositoryModule.forFeature() mapping the entity key to your cache entity class.

import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { CoreModule, Operation } from '@concepta/nestjs-core';
import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud';
import { RepositoryModule } from '@concepta/nestjs-repository';
import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm';
import {
  CacheInterface,
  CacheModule,
  CacheNamespace,
  cacheCreateSchema,
  cacheUpdateSchema,
  cacheSchema,
} from '@concepta/nestjs-cache';
import {
  cachePaginatedSchema,
  CreateCacheRequest,
  CreateCacheRequestHandler,
  UpdateCacheRequest,
  UpdateCacheRequestHandler,
  ReplaceCacheRequest,
  ReplaceCacheRequestHandler,
  DeleteCacheRequest,
  DeleteCacheRequestHandler,
  ListCachesRequest,
  ListCachesRequestHandler,
  ReadCacheRequest,
  ReadCacheRequestHandler,
} from '@concepta/nestjs-cache/optional/crud';

@Module({
  imports: [
    CqrsModule.forRoot(),
    RepositoryModule.forRoot({}),
    CrudModule.forRoot({
      defaultResolver: CrudCqrsResolver,
    }),
    CoreModule.forRoot(),
    RepositoryModule.forFeature({
      module: TypeOrmRepositoryModule,
      entities: [{ key: 'userCache', entity: UserCacheEntity }],
    }),
    CacheModule.forFeature(['userCache']),
    CrudModule.forFeature<CacheInterface>({
      crud: {
        controller: {
          entity: 'userCache',
          path: 'cache/user',
          resolver: CrudCqrsResolver,
          transactional: true,
          extraDecorators: [CacheNamespace({ name: 'userCache' })],
          request: { body: cacheCreateSchema },
          response: {
            resource: cacheSchema,
            paginated: cachePaginatedSchema,
          },
        },
        operations: [
          {
            operation: Operation.List,
            query: ListCachesRequest,
            queryHandler: ListCachesRequestHandler,
          },
          {
            operation: Operation.Read,
            query: ReadCacheRequest,
            queryHandler: ReadCacheRequestHandler,
          },
          {
            operation: Operation.Create,
            request: { body: cacheCreateSchema },
            command: CreateCacheRequest,
            commandHandler: CreateCacheRequestHandler,
          },
          {
            operation: Operation.Update,
            request: { body: cacheUpdateSchema },
            command: UpdateCacheRequest,
            commandHandler: UpdateCacheRequestHandler,
          },
          {
            operation: Operation.Replace,
            request: { body: cacheCreateSchema },
            command: ReplaceCacheRequest,
            commandHandler: ReplaceCacheRequestHandler,
          },
          {
            operation: Operation.Delete,
            command: DeleteCacheRequest,
            commandHandler: DeleteCacheRequestHandler,
          },
        ],
      },
    }),
  ],
})
export class UserCacheModule {}

This registers a CRUD controller at /cache/user with List, Read, Create, Update, Replace, and Delete operations. The CrudCqrsResolver bridges HTTP requests to domain commands and queries via the CQRS bus. Set transactional: true to wrap each operation in a database transaction.

CacheModule.forRoot() (or forRootAsync()) must be registered globally in a parent module for forFeature() to resolve its dependencies.

This is a minimal example. CrudModule.forFeature() supports additional options including custom resolvers, route guards, schema-based per-operation serialization overrides, and per-operation request overrides. See the @concepta/nestjs-crud documentation for the full API.

OpenAPI

Create the swagger document with the standardSchemaConverter from @concepta/nestjs-core:

SwaggerModule.createDocument(app, config, { standardSchemaConverter });

The cache schemas register as bare OpenAPI component ids: Cache (cacheSchema) and CachePaginated (cachePaginatedSchema). Request body schemas are documented inline.

Entry Points

| Import Path | Contents | | --- | --- | | @concepta/nestjs-cache | Module, aggregate, commands, queries, events, handlers, schemas, expiration policy, repository, exceptions, domain interfaces | | @concepta/nestjs-cache/optional/crud | CRUD request/handler classes, paginated schema | | @concepta/nestjs-cache/optional/typeorm | CacheSqliteEntity, CachePostgresEntity | | @concepta/nestjs-cache/optional/seeding | CacheFactory |

Seeding

A CacheFactory is available for test seeding:

import { CacheFactory } from '@concepta/nestjs-cache/optional/seeding';

Environment Variables

| Variable | Default | Description | | --- | --- | --- | | CACHE_EXPIRE_IN | null | Default expiration time span for cache entries |