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

@hazeljs/cache

v1.0.2

Published

Caching module for HazelJS framework

Readme

@hazeljs/cache

Cache with one decorator. Invalidate with another.

Memory, Redis, or hybrid. @Cache for results, @CacheEvict for invalidation. Tag-based, TTL, warming, stats. Stop writing cache logic by hand.

npm version npm downloads License: Apache-2.0

Features

  • 💾 Multiple Strategies - Memory, Redis, and CDN caching
  • 🎨 Decorator-Based API - @Cache, @CacheEvict, @CacheTTL
  • 🏷️ Tag-Based Management - Group and invalidate related caches
  • TTL Support - Automatic expiration
  • 🔄 Cache Warming - Pre-populate cache on startup
  • 📊 Statistics - Hit/miss rates and performance metrics
  • 🔑 Key Generation - Automatic and custom key generation
  • 🧹 Auto Cleanup - Automatic removal of expired entries
  • 🔒 Distributed Locking - Prevent cache stampede with @CacheLock
  • 📥 Cache-Aside Pattern - Automatic get/set logic with @CacheAside
  • ✏️ Write-Through/Behind - Immediate or queued cache updates with @CacheWrite
  • 🔥 Smart Warming - Scheduled cache population with @CacheWarm
  • 📡 Event System - Cache hit/miss/eviction events
  • 🏥 Health Monitoring - Built-in health checks and metrics

Installation

npm install @hazeljs/cache

Optional Dependencies

# For Redis caching
npm install ioredis

Quick Start

Memory Cache

import { CacheModule } from '@hazeljs/cache';

@HazelModule({
  imports: [
    CacheModule.forRoot({
      strategy: 'memory',
      ttl: 3600, // 1 hour
      max: 1000, // Max 1000 entries
    }),
  ],
})
export class AppModule {}

Using Cache Decorator

import { Injectable } from '@hazeljs/core';
import { Cache, CacheEvict } from '@hazeljs/cache';

@Injectable()
export class ProductService {
  @Cache({
    key: 'product-{id}',
    ttl: 3600,
  })
  async findOne(id: string) {
    // Expensive database query - cached for 1 hour
    return await this.db.product.findUnique({ where: { id } });
  }

  @Cache({
    key: 'products-all',
    ttl: 1800,
  })
  async findAll() {
    return await this.db.product.findMany();
  }

  @CacheEvict({
    keys: ['product-{id}', 'products-all'],
  })
  async update(id: string, data: any) {
    return await this.db.product.update({
      where: { id },
      data,
    });
  }

  @CacheEvict({
    keys: ['product-*'], // Wildcard pattern
  })
  async deleteAll() {
    return await this.db.product.deleteMany();
  }
}

Cache Strategies

Memory Cache (Development)

CacheModule.forRoot({
  strategy: 'memory',
  ttl: 3600,
  max: 1000,
  updateAgeOnGet: true,
});

Best for: Development, testing, single-instance applications

Redis Cache (Production)

import Redis from 'ioredis';

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  password: 'your-password',
  db: 0,
});

CacheModule.forRoot({
  strategy: 'redis',
  redis: redis,
  ttl: 3600,
  keyPrefix: 'myapp:',
});

Best for: Production, distributed systems, shared cache

Hybrid Cache

CacheModule.forRoot({
  strategy: 'hybrid',
  memory: {
    ttl: 300, // 5 minutes in memory
    max: 100,
  },
  redis: {
    client: redis,
    ttl: 3600, // 1 hour in Redis
    keyPrefix: 'myapp:',
  },
});

Best for: High-performance applications, frequently accessed data

Decorators

@Cache()

Cache method results:

@Cache({
  key: 'user-{id}',
  ttl: 3600,
  strategy: 'redis',
})
async getUser(id: string) {
  return await this.userService.findOne(id);
}

// Dynamic key generation
@Cache({
  key: (args) => `search-${args[0]}-${args[1]}`,
  ttl: 1800,
})
async search(query: string, page: number) {
  return await this.searchService.search(query, page);
}

@CacheEvict()

Invalidate cache entries:

// Evict specific keys
@CacheEvict({
  keys: ['user-{id}'],
})
async updateUser(id: string, data: any) {
  return await this.userService.update(id, data);
}

// Evict multiple keys
@CacheEvict({
  keys: ['user-{id}', 'users-all', 'users-active'],
})
async deleteUser(id: string) {
  return await this.userService.delete(id);
}

// Evict by pattern
@CacheEvict({
  keys: ['user-*'],
})
async clearAllUsers() {
  return await this.userService.deleteAll();
}

// Evict by tags
@CacheEvict({
  tags: ['users', 'profiles'],
})
async updateUserProfile(id: string, data: any) {
  return await this.userService.updateProfile(id, data);
}

@CacheTTL()

Set TTL dynamically:

@Cache({ key: 'data-{id}' })
@CacheTTL((result) => {
  // Cache premium users for 1 hour, others for 5 minutes
  return result.isPremium ? 3600 : 300;
})
async getData(id: string) {
  return await this.dataService.find(id);
}

Tag-Based Caching

Group related cache entries with tags:

@Injectable()
export class ProductService {
  @Cache({
    key: 'product-{id}',
    tags: ['products', 'catalog'],
    ttl: 3600,
  })
  async findOne(id: string) {
    return await this.db.product.findUnique({ where: { id } });
  }

  @Cache({
    key: 'products-featured',
    tags: ['products', 'featured'],
    ttl: 1800,
  })
  async findFeatured() {
    return await this.db.product.findMany({ where: { featured: true } });
  }

  // Invalidate all product-related caches
  @CacheEvict({
    tags: ['products'],
  })
  async updateProduct(id: string, data: any) {
    return await this.db.product.update({ where: { id }, data });
  }
}

Direct Cache Service Usage

import { Injectable } from '@hazeljs/core';
import { CacheService } from '@hazeljs/cache';

@Injectable()
export class MyService {
  constructor(private cacheService: CacheService) {}

  async getData(key: string) {
    // Get from cache
    const cached = await this.cacheService.get(key);
    if (cached) return cached;

    // Fetch data
    const data = await this.fetchData(key);

    // Store in cache
    await this.cacheService.set(key, data, 3600);

    return data;
  }

  async invalidate(key: string) {
    await this.cacheService.del(key);
  }

  async invalidatePattern(pattern: string) {
    await this.cacheService.delPattern(pattern);
  }

  async invalidateTags(tags: string[]) {
    await this.cacheService.delByTags(tags);
  }
}

Cache Warming

Pre-populate cache on application startup:

import { Injectable, OnModuleInit } from '@hazeljs/core';
import { CacheService } from '@hazeljs/cache';

@Injectable()
export class CacheWarmer implements OnModuleInit {
  constructor(
    private cacheService: CacheService,
    private productService: ProductService
  ) {}

  async onModuleInit() {
    // Warm up featured products cache
    const featured = await this.productService.findFeatured();
    await this.cacheService.set('products-featured', featured, 3600);

    // Warm up categories cache
    const categories = await this.productService.findCategories();
    await this.cacheService.set('categories-all', categories, 7200);

    console.log('Cache warmed up successfully');
  }
}

Statistics

Monitor cache performance:

const stats = await cacheService.getStats();

console.log('Hits:', stats.hits);
console.log('Misses:', stats.misses);
console.log('Hit Rate:', stats.hitRate);
console.log('Total Keys:', stats.keys);
console.log('Memory Usage:', stats.memoryUsage);

Advanced Configuration

Custom Key Generator

CacheModule.forRoot({
  strategy: 'redis',
  redis: redis,
  keyGenerator: (target, methodName, args) => {
    return `${target.constructor.name}:${methodName}:${JSON.stringify(args)}`;
  },
});

Conditional Caching

@Cache({
  key: 'user-{id}',
  condition: (args, result) => {
    // Only cache if user is active
    return result?.status === 'active';
  },
})
async getUser(id: string) {
  return await this.userService.findOne(id);
}

Cache Compression

CacheModule.forRoot({
  strategy: 'redis',
  redis: redis,
  compression: {
    enabled: true,
    threshold: 1024, // Compress if > 1KB
  },
});

API Reference

CacheService

class CacheService {
  get<T>(key: string): Promise<T | null>;
  set(key: string, value: any, ttl?: number): Promise<void>;
  del(key: string): Promise<void>;
  delPattern(pattern: string): Promise<void>;
  delByTags(tags: string[]): Promise<void>;
  has(key: string): Promise<boolean>;
  clear(): Promise<void>;
  getStats(): Promise<CacheStats>;
  keys(pattern?: string): Promise<string[]>;
}

Decorators

@Cache({
  key: string | ((args: any[]) => string);
  ttl?: number;
  strategy?: 'memory' | 'redis' | 'hybrid';
  tags?: string[];
  condition?: (args: any[], result: any) => boolean;
})

@CacheEvict({
  keys?: string[];
  tags?: string[];
  allEntries?: boolean;
})

@CacheTTL((result: any) => number)

Advanced Features

Distributed Cache Locking

Prevent cache stampede when multiple requests try to compute the same value:

import { CacheLock } from '@hazeljs/cache';

@Injectable()
export class ProductService {
  @CacheLock({
    key: 'product-{id}',
    ttl: 30000, // 30 seconds
    retryDelay: 1000,
    maxRetries: 3,
  })
  async expensiveOperation(id: string) {
    // Only one instance will execute at a time
    return await this.computeExpensiveResult(id);
  }
}

Cache-Aside Pattern

Automatic get/set logic with fallback support:

import { CacheAside, CacheAsideWithFallback } from '@hazeljs/cache';

@Injectable()
export class UserService {
  @CacheAside({
    key: 'user-{id}',
    ttl: 3600,
    fallback: () => this.db.user.findDefault(),
  })
  async getUser(id: string) {
    return await this.db.user.findUnique({ where: { id } });
  }

  @CacheAsideWithFallback({
    key: 'user-{id}',
    ttl: 1800,
    fallbackValue: { id: 'unknown', name: 'Guest User' },
  })
  async getUserWithFallback(id: string) {
    return await this.db.user.findUnique({ where: { id } });
  }
}

Write-Through/Write-Behind Caching

Control when cache updates happen:

import { WriteThrough, WriteBehind } from '@hazeljs/cache';

@Injectable()
export class ProductService {
  @WriteThrough({
    key: 'product-{id}',
    ttl: 3600,
  })
  async updateProduct(id: string, data: any) {
    // Cache updated immediately
    return await this.db.product.update({ where: { id }, data });
  }

  @WriteBehind({
    key: 'product-{id}',
    ttl: 3600,
    async: true,
  })
  async updateProductAsync(id: string, data: any) {
    // Cache update queued for better performance
    return await this.db.product.update({ where: { id }, data });
  }
}

Smart Cache Warming

Pre-populate cache on schedule:

import { CacheWarm, CacheWarmingUtils } from '@hazeljs/cache';

@Injectable()
export class CacheService {
  @CacheWarm({
    keys: ['featured-products', 'categories'],
    fetcher: async (key: string) => {
      if (key === 'featured-products') {
        return await this.productService.findFeatured();
      }
      return await this.categoryService.findAll();
    },
    ttl: 7200, // 2 hours
    parallel: true,
    schedule: '0 */6 * * *', // Every 6 hours
    condition: 'low-traffic', // Only warm during low traffic hours
  })
  async warmCache() {
    // This method will be executed on schedule
  }
}

// Manual warming
await CacheWarmingUtils.warmUp('CacheService.warmCache');

Event System

Listen to cache events:

import { EventEmitter } from 'eventemitter3';

const cacheService = new CacheService('memory');

// Listen to cache events
cacheService.on('hit', (key) => {
  console.log(`Cache hit: ${key}`);
});

cacheService.on('miss', (key) => {
  console.log(`Cache miss: ${key}`);
});

cacheService.on('eviction', (key) => {
  console.log(`Cache eviction: ${key}`);
});

Use Cases

API Response Caching

@Controller('/api')
export class ApiController {
  @Get('/products')
  @Cache({
    key: 'api-products-{page}-{limit}',
    ttl: 300,
    tags: ['api', 'products'],
  })
  async getProducts(@Query('page') page: number, @Query('limit') limit: number) {
    return await this.productService.findAll(page, limit);
  }
}

Database Query Caching

@Injectable()
export class UserRepository {
  @Cache({
    key: 'db-user-{id}',
    ttl: 3600,
    tags: ['database', 'users'],
  })
  async findById(id: string) {
    return await this.prisma.user.findUnique({ where: { id } });
  }

  @CacheEvict({
    keys: ['db-user-{id}'],
    tags: ['users'],
  })
  async update(id: string, data: any) {
    return await this.prisma.user.update({ where: { id }, data });
  }
}

Computed Results Caching

@Injectable()
export class AnalyticsService {
  @Cache({
    key: 'analytics-{startDate}-{endDate}',
    ttl: 7200, // 2 hours
    tags: ['analytics'],
  })
  async getReport(startDate: string, endDate: string) {
    // Expensive computation
    return await this.computeAnalytics(startDate, endDate);
  }
}

Best Practices

  • Use appropriate TTL - Short TTL for frequently changing data, long TTL for static data
  • Tag related caches - Group caches that should be invalidated together
  • Monitor hit rates - Adjust caching strategy based on statistics
  • Use Redis for production - Memory cache is only for development
  • Invalidate proactively - Clear cache when data changes
  • Avoid caching user-specific data - Unless using user-specific keys

Examples

See the examples directory for complete working examples.

Testing

npm test

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

License

Apache 2.0 © HazelJS

Links