{"_id":"@dtjldamien/nestjs-cache-service","_rev":"2-ec87ce68eee61a315a15f8d7b64bfb7e","name":"@dtjldamien/nestjs-cache-service","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@dtjldamien/nestjs-cache-service","version":"1.0.0","keywords":["nestjs","cache","redis","cache-manager","write-lock","thundering-herd","cache-aside","async-lock","typescript"],"author":"","license":"MIT","_id":"@dtjldamien/nestjs-cache-service@1.0.0","maintainers":[{"name":"dtjldamien","email":"dtjldamien@gmail.com"}],"homepage":"https://github.com/dtjldamien/nestjs-cache-service#readme","bugs":{"url":"https://github.com/dtjldamien/nestjs-cache-service/issues"},"dist":{"shasum":"9ed030113b5cb8ca3a8f31accee932d4bc1cc084","tarball":"https://registry.npmjs.org/@dtjldamien/nestjs-cache-service/-/nestjs-cache-service-1.0.0.tgz","fileCount":32,"integrity":"sha512-xxlUroTtSRo8T+h+yfiuQscSJkNYq9GJJ1cFKyFlhXfn74Iy2FJ0QPAK8SFAvwJnl0+CSnLq4t2nLShaviftcQ==","signatures":[{"sig":"MEYCIQDxqx+/zImegheMt6wZIugA2oJ4+ftmCBJWdJflYHus8AIhALXQP3hAJUs4IRmmKFbeI9pxl6TBh3pJ4+yVaDut48Q/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57018},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.0.0","pnpm":">=9.0.0"},"gitHead":"e63fab0217a30942a45694ff9f779da2dd29b741","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest","build":"tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","prepare":"husky","lint:fix":"eslint \"src/**/*.ts\" \"test/**/*.ts\" --fix","test:cov":"jest --coverage","typecheck":"tsc --noEmit","test:watch":"jest --watch","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\"","prepublishOnly":"pnpm build && pnpm test"},"_npmUser":{"name":"dtjldamien","email":"dtjldamien@gmail.com"},"repository":{"url":"git+https://github.com/dtjldamien/nestjs-cache-service.git","type":"git"},"_npmVersion":"10.9.2","description":"NestJS cache service with write-locking to prevent thundering herd","directories":{},"_nodeVersion":"22.14.0","dependencies":{"async-lock":"^1.4.1"},"_hasShrinkwrap":false,"packageManager":"pnpm@9.0.0","devDependencies":{"jest":"^30.2.0","husky":"^9.1.7","eslint":"^9.19.0","ts-jest":"^29.4.6","prettier":"^3.8.1","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^25.0.10","cache-manager":"^7.2.8","@nestjs/common":"^11.1.12","@nestjs/testing":"^11.1.11","@types/async-lock":"^1.4.2","@nestjs/cache-manager":"^3.1.0","@typescript-eslint/parser":"^8.27.0","@typescript-eslint/eslint-plugin":"^8.27.0"},"peerDependencies":{"cache-manager":"^7.0.0","@nestjs/common":"^11.0.0","reflect-metadata":"^0.2.0","@nestjs/cache-manager":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/nestjs-cache-service_1.0.0_1769530395250_0.6569820230369074","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@dtjldamien/nestjs-cache-service","version":"1.0.2","description":"NestJS cache service with write-locking to prevent thundering herd","main":"dist/index.js","types":"dist/index.d.ts","keywords":["nestjs","cache","redis","cache-manager","write-lock","thundering-herd","cache-aside","async-lock","typescript"],"author":"","license":"MIT","repository":{"type":"git","url":"git+https://github.com/dtjldamien/nestjs-cache-service.git"},"bugs":{"url":"https://github.com/dtjldamien/nestjs-cache-service/issues"},"homepage":"https://github.com/dtjldamien/nestjs-cache-service#readme","engines":{"node":">=22.0.0","pnpm":">=9.0.0"},"peerDependencies":{"@nestjs/cache-manager":"^3.0.0","@nestjs/common":"^11.0.0","cache-manager":"^7.0.0","keyv":"^5.0.0","reflect-metadata":"^0.2.0"},"dependencies":{"async-lock":"^1.4.1"},"devDependencies":{"@keyv/redis":"^5.1.6","@nestjs/cache-manager":"^3.1.0","@nestjs/common":"^11.1.12","@nestjs/testing":"^11.1.11","@types/async-lock":"^1.4.2","@types/jest":"^30.0.0","@types/node":"^25.0.10","@typescript-eslint/eslint-plugin":"^8.27.0","@typescript-eslint/parser":"^8.27.0","cache-manager":"^7.2.8","eslint":"^9.19.0","husky":"^9.1.7","jest":"^30.2.0","keyv":"^5.6.0","prettier":"^3.8.1","ts-jest":"^29.4.6","typescript":"^5.9.3"},"scripts":{"build":"tsc -p tsconfig.build.json","test":"jest","test:watch":"jest --watch","test:cov":"jest --coverage","test:types":"pnpm typecheck && pnpm test cache-types","test:all":"pnpm typecheck && pnpm test","lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","lint:fix":"eslint \"src/**/*.ts\" \"test/**/*.ts\" --fix","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\"","typecheck":"tsc --noEmit"},"_id":"@dtjldamien/nestjs-cache-service@1.0.2","_integrity":"sha512-QiTtJ2irR9NBBKnZ0pN04mUQqoHxLyfiFYwASBkK6XRFIiIQSy0kR7ojALpFXAKGI9Kx/4uUzy0J8mYhD2SKbg==","_resolved":"/tmp/18b14e82537026a52ec90018502c23ae/dtjldamien-nestjs-cache-service-1.0.2.tgz","_from":"file:dtjldamien-nestjs-cache-service-1.0.2.tgz","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-QiTtJ2irR9NBBKnZ0pN04mUQqoHxLyfiFYwASBkK6XRFIiIQSy0kR7ojALpFXAKGI9Kx/4uUzy0J8mYhD2SKbg==","shasum":"544b6ea885a787b1a9e7f5fa3f8dfb45dcac1101","tarball":"https://registry.npmjs.org/@dtjldamien/nestjs-cache-service/-/nestjs-cache-service-1.0.2.tgz","fileCount":32,"unpackedSize":60116,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDHbMr09ZxRIPjvATnl748REPBM4TBnEflQ9rQPJT9lIgIhAP0gOvty5il2JbM65eJo8t/PqIOnlXf06I+7b8Hht2y3"}]},"_npmUser":{"name":"dtjldamien","email":"dtjldamien@gmail.com"},"directories":{},"maintainers":[{"name":"dtjldamien","email":"dtjldamien@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nestjs-cache-service_1.0.2_1769532900721_0.7149976898409198"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-27T16:13:15.177Z","modified":"2026-01-27T16:55:00.997Z","1.0.0":"2026-01-27T16:13:15.388Z","1.0.2":"2026-01-27T16:55:00.890Z"},"bugs":{"url":"https://github.com/dtjldamien/nestjs-cache-service/issues"},"license":"MIT","homepage":"https://github.com/dtjldamien/nestjs-cache-service#readme","keywords":["nestjs","cache","redis","cache-manager","write-lock","thundering-herd","cache-aside","async-lock","typescript"],"repository":{"type":"git","url":"git+https://github.com/dtjldamien/nestjs-cache-service.git"},"description":"NestJS cache service with write-locking to prevent thundering herd","maintainers":[{"name":"dtjldamien","email":"dtjldamien@gmail.com"}],"readme":"# nestjs-cache-service\n\n[![CI](https://github.com/dtjldamien/nestjs-cache-service/actions/workflows/ci.yml/badge.svg)](https://github.com/dtjldamien/nestjs-cache-service/actions/workflows/ci.yml)\n[![npm version](https://badge.fury.io/js/nestjs-cache-service.svg)](https://badge.fury.io/js/nestjs-cache-service)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA NestJS cache service with write-locking to prevent the thundering herd problem.\n\n## Features\n\n- **Write-locking**: Prevents multiple concurrent requests from triggering duplicate expensive operations\n- **Cache-aside pattern**: Automatic cache population with `getOrSet()`\n- **Multiple storage backends**: Works with Redis, PostgreSQL, MySQL, MongoDB, SQLite, Memcached, and more via [Keyv adapters](https://keyv.org/docs/storage-adapters/)\n- **Multi-layer caching**: Support for L1 (in-memory) and L2 (persistent) cache layers\n- **TypeScript**: Full type safety and IntelliSense support\n- **Test coverage**: Comprehensive test coverage\n\n## Installation\n\n```bash\n# Core dependencies\npnpm add @dtjldamien/nestjs-cache-service @nestjs/cache-manager cache-manager keyv\n\n# Storage adapters (choose based on your needs)\npnpm add @keyv/redis      # For Redis\npnpm add @keyv/postgres   # For PostgreSQL\npnpm add @keyv/mysql      # For MySQL\npnpm add @keyv/mongo      # For MongoDB\npnpm add @keyv/sqlite     # For SQLite\n```\n\n## Quick Start\n\n### 1. Import the CacheModule\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { CacheModule } from '@dtjldamien/nestjs-cache-service';\n\n@Module({\n  imports: [\n    CacheModule.registerAsync({\n      useFactory: async () => ({\n        // In-memory cache (default)\n      }),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### 2. Use CacheService in your services\n\n```typescript\nimport { Injectable } from '@nestjs/common';\nimport { CacheService } from '@dtjldamien/nestjs-cache-service';\n\n@Injectable()\nexport class UserService {\n  constructor(private cacheService: CacheService) {}\n\n  async getUser(userId: string) {\n    return this.cacheService.getOrSet(\n      `user:${userId}`,\n      async () => {\n        // This expensive operation will only run once\n        // even if multiple requests arrive simultaneously\n        return this.fetchUserFromDatabase(userId);\n      },\n      3600000, // 1 hour TTL\n    );\n  }\n\n  private async fetchUserFromDatabase(userId: string) {\n    // Your database query here\n  }\n}\n```\n\n## Configuration\n\n### Storage Options\n\nThis package uses [Keyv](https://keyv.org/) for storage management, supporting multiple backends including Redis, PostgreSQL, MySQL, MongoDB, SQLite, and more. See the [Keyv documentation](https://keyv.org/docs/storage-adapters/) for all available adapters.\n\n### Redis Configuration Examples\n\n#### Basic Redis Setup\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { CacheModule } from '@dtjldamien/nestjs-cache-service';\nimport KeyvRedis from '@keyv/redis';\n\n@Module({\n  imports: [\n    CacheModule.registerAsync({\n      useFactory: async () => ({\n        stores: [new KeyvRedis('redis://localhost:6379')],\n        ttl: 3600000, // 1 hour\n      }),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n#### Redis with Options\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { CacheModule } from '@dtjldamien/nestjs-cache-service';\nimport { Keyv } from 'keyv';\nimport KeyvRedis from '@keyv/redis';\n\n@Module({\n  imports: [\n    CacheModule.registerAsync({\n      useFactory: async () => ({\n        stores: [\n          new Keyv({\n            store: new KeyvRedis({\n              url: 'redis://localhost:6379',\n              // Additional Redis options\n              socket: {\n                connectTimeout: 5000,\n                keepAlive: true,\n              },\n            }),\n            ttl: 3600000,\n          }),\n        ],\n      }),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### Environment-based Configuration\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { CacheModule } from '@dtjldamien/nestjs-cache-service';\nimport { Keyv } from 'keyv';\nimport KeyvRedis from '@keyv/redis';\n\n@Module({\n  imports: [\n    CacheModule.registerAsync({\n      useFactory: async () => {\n        // Use in-memory cache for testing\n        if (process.env.NODE_ENV === 'test') {\n          return {};\n        }\n\n        // Use Redis for production\n        return {\n          stores: [\n            new Keyv({\n              store: new KeyvRedis(`redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`),\n            }),\n          ],\n        };\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### Multi-Layer Caching\n\nCombine multiple stores for optimal performance with L1 in-memory cache and L2 persistent storage:\n\n```typescript\nimport { CacheModule } from '@dtjldamien/nestjs-cache-service';\nimport { Keyv } from 'keyv';\nimport KeyvRedis from '@keyv/redis';\n\n@Module({\n  imports: [\n    CacheModule.registerAsync({\n      useFactory: () => ({\n        stores: [\n          // L1: Fast in-memory cache (short TTL)\n          new Keyv({ ttl: 60000 }), // 1 minute\n          // L2: Redis for persistence (longer TTL)\n          new Keyv({\n            store: new KeyvRedis('redis://localhost:6379'),\n            ttl: 3600000, // 1 hour\n          }),\n        ],\n      }),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### With Dependency Injection\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { CacheModule } from '@dtjldamien/nestjs-cache-service';\nimport { Keyv } from 'keyv';\nimport KeyvRedis from '@keyv/redis';\n\n@Module({\n  imports: [\n    ConfigModule.forRoot(),\n    CacheModule.registerAsync({\n      imports: [ConfigModule],\n      inject: [ConfigService],\n      useFactory: async (configService: ConfigService) => {\n        const redisHost = configService.get('REDIS_HOST');\n        const redisPort = configService.get('REDIS_PORT');\n\n        return {\n          stores: [\n            new Keyv({\n              store: new KeyvRedis(`redis://${redisHost}:${redisPort}`),\n            }),\n          ],\n          ttl: 3600000, // Default 1 hour\n        };\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n## API Reference\n\n### CacheService\n\n#### `get<T>(key: string): Promise<T | null>`\n\nGet a value from cache. If a write operation is in progress for this key, this method will wait for the write to complete before reading.\n\n```typescript\nconst value = await cacheService.get<UserData>('user:123');\n```\n\n#### `set(key: string, value: any, ttl: number): Promise<void>`\n\nSet a value in cache with TTL (in milliseconds). This operation is protected by a write lock.\n\n```typescript\nawait cacheService.set('user:123', userData, 3600000); // 1 hour\n```\n\n#### `del(key: string): Promise<void>`\n\nDelete a value from cache.\n\n```typescript\nawait cacheService.del('user:123');\n```\n\n#### `clear(): Promise<void>`\n\nClear all values from cache.\n\n```typescript\nawait cacheService.clear();\n```\n\n#### `getOrSet<T>(key: string, factory: () => Promise<T>, ttl: number): Promise<T>`\n\nCache-aside pattern: Get from cache or execute factory function and cache the result.\n\nThis method prevents the thundering herd problem. If multiple requests arrive for the same uncached key simultaneously, only one will execute the factory function while others wait.\n\n```typescript\nconst userData = await cacheService.getOrSet(\n  `user:${userId}`,\n  async () => {\n    // This will only be called once, even with concurrent requests\n    return await userRepository.findById(userId);\n  },\n  3600000, // 1 hour\n);\n```\n\n## Usage Patterns\n\n### Basic Caching\n\n```typescript\n// Manual cache management\nconst cached = await cacheService.get<string>('my-key');\nif (!cached) {\n  const data = await expensiveOperation();\n  await cacheService.set('my-key', data, 60000);\n  return data;\n}\nreturn cached;\n```\n\n### Cache-Aside Pattern (Recommended)\n\n```typescript\n// Automatic cache management with thundering herd prevention\nconst data = await cacheService.getOrSet('my-key', async () => expensiveOperation(), 60000);\n```\n\n### Cache Invalidation\n\n```typescript\n// Delete specific key\nawait cacheService.del('user:123');\n\n// Clear all cache\nawait cacheService.clear();\n```\n\n### TTL Examples\n\n```typescript\n// 1 minute\nawait cacheService.set('key', value, 60 * 1000);\n\n// 1 hour\nawait cacheService.set('key', value, 60 * 60 * 1000);\n\n// 24 hours\nawait cacheService.set('key', value, 24 * 60 * 60 * 1000);\n```\n\n## Thundering Herd Prevention\n\nThe thundering herd problem occurs when multiple requests simultaneously attempt to regenerate the same expired cache entry, causing:\n\n- Multiple expensive operations (API calls, database queries)\n- Increased load on backend services\n- Slower response times\n\nThis package solves this by using write locks:\n\n```typescript\n// Without write-locking (BAD):\n// 10 simultaneous requests = 10 API calls\n\n// With write-locking (GOOD):\n// 10 simultaneous requests = 1 API call\nconst data = await cacheService.getOrSet(\n  'expensive-key',\n  async () => apiCall(), // Called only once\n  3600000,\n);\n```\n\n## How It Works\n\n1. **First request** arrives for uncached key\n   - Acquires write lock\n   - Executes factory function\n   - Caches result\n   - Releases lock\n\n2. **Concurrent requests** for same uncached key\n   - Wait for write lock\n   - Check cache again after lock acquired\n   - Return cached value (no factory execution)\n\n3. **Subsequent requests** for cached key\n   - Return cached value immediately\n   - No lock acquisition needed\n\n## Testing\n\n```bash\n# Run tests\npnpm test\n\n# Run tests with coverage\npnpm test:cov\n\n# Run tests in watch mode\npnpm test:watch\n```\n\n## Building\n\n```bash\n# Build the package\npnpm build\n\n# The output will be in the dist/ directory\n```\n\n## Migration Guide\n\n### From Manual Cache Implementation\n\n**Before:**\n\n```typescript\nimport { CACHE_MANAGER } from '@nestjs/cache-manager';\nimport { Cache } from 'cache-manager';\n\nconstructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}\n\nasync getData() {\n  const cached = await this.cacheManager.get('key');\n  if (cached) return cached;\n\n  const data = await expensiveOp();\n  await this.cacheManager.set('key', data, 60000);\n  return data;\n}\n```\n\n**After:**\n\n```typescript\nimport { CacheService } from '@dtjldamien/nestjs-cache-service';\n\nconstructor(private cacheService: CacheService) {}\n\nasync getData() {\n  return this.cacheService.getOrSet(\n    'key',\n    async () => expensiveOp(),\n    60000,\n  );\n}\n```\n\n### From Internal CacheService\n\nIf you're migrating from an internal implementation:\n\n1. Update imports:\n\n```typescript\n// Before\nimport { CacheService } from './shared/services/cache.service';\n\n// After\nimport { CacheService } from '@dtjldamien/nestjs-cache-service';\n```\n\n2. Update module imports:\n\n```typescript\n// Before\nimport { CacheModule } from '@nestjs/cache-manager';\n\n@Module({\n  imports: [CacheModule.registerAsync({ ... })],\n  providers: [CacheService],\n  exports: [CacheService],\n})\n\n// After\nimport { CacheModule } from '@dtjldamien/nestjs-cache-service';\n\n@Module({\n  imports: [CacheModule.registerAsync({ ... })],\n  // CacheService is now provided by CacheModule\n})\n```\n\n3. The API remains identical - no code changes needed in services using CacheService\n\n## License\n\nMIT\n\n## Contributing\n\nWe welcome contributions to improve this package. Please follow these guidelines:\n\n### Development Setup\n\n1. Fork and clone the repository\n2. Install dependencies:\n   ```bash\n   pnpm install\n   ```\n3. Create a feature branch:\n   ```bash\n   git checkout -b feature/your-feature-name\n   ```\n\n### Code Standards\n\n- Follow the NestJS module best practices documented in [AGENTS.md](./AGENTS.md)\n- Write TypeScript with strict type safety (no `any` types)\n- Maintain 100% test coverage for new features\n- Use meaningful commit messages following conventional commits format\n\n### Before Submitting\n\n1. **Run tests**: Ensure all tests pass\n\n   ```bash\n   pnpm test\n   ```\n\n2. **Check coverage**: Maintain 100% coverage\n\n   ```bash\n   pnpm test --coverage\n   ```\n\n3. **Build successfully**: Verify the build works\n\n   ```bash\n   pnpm build\n   ```\n\n4. **Lint and format**: Code should pass linting\n   ```bash\n   pnpm lint\n   ```\n\n### Pull Request Process\n\n1. Update the README.md with details of changes if needed\n2. Add tests for any new functionality\n3. Ensure the PR description clearly describes the problem and solution\n4. Reference any related issues in the PR description\n\n### Code Review Guidelines\n\n- Code must follow TypeScript best practices (see [AGENTS.md](./AGENTS.md))\n- All public APIs must have JSDoc comments\n- Breaking changes require a major version bump\n- New features should include usage examples\n\nFor detailed NestJS and TypeScript best practices, see [AGENTS.md](./AGENTS.md).\n\n## Support\n\nFor questions or issues, please open an issue in the repository or contact the maintainers.\n","readmeFilename":"README.md"}