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

@hemia/cache-manager

v0.0.5

Published

Manejador de caché con soporte Redis y inversify para aplicaciones TypeScript.

Readme

@hemia/cache-manager

Una librería sencilla y extensible para manejar caché utilizando Redis a través de ioredis. Totalmente independiente de frameworks de inyección de dependencias, pero compatible con InversifyJS y otros sistemas DI.


Características

  • Independiente: Sin dependencias de frameworks DI
  • 🔧 Flexible: Úsalo con o sin inyección de dependencias
  • 🚀 TypeScript: Completamente tipado
  • 📦 Ligero: Solo depende de ioredis
  • 🔌 Compatible: Funciona con InversifyJS, Awilix, TSyringe, etc.

Instalación

npm install @hemia/cache-manager

Uso Básico (sin inyección de dependencias)

import { CacheClient, CacheService } from '@hemia/cache-manager';

// 1. Crear el cliente de Redis
const cacheClient = new CacheClient({
  host: 'localhost',
  port: 6379,
  password: 'your-password',
  db: 0,
  tls: false,
});

// 2. Crear el servicio de cache
const cacheService = new CacheService(cacheClient);

// 3. Usar el servicio
async function example() {
  // Guardar un string
  await cacheService.setString('user:123', 'John Doe', 3600);
  
  // Obtener un string
  const name = await cacheService.getString('user:123');
  console.log(name); // 'John Doe'
  
  // Guardar un objeto
  await cacheService.setObject('user:data:123', { id: 123, name: 'John' }, 3600);
  
  // Obtener un objeto
  const user = await cacheService.getObject('user:data:123');
  console.log(user); // { id: 123, name: 'John' }
}

Uso con InversifyJS

1. Instalación adicional

npm install inversify reflect-metadata

2. Crear módulo de cache en tu backend

Crea src/infrastructure/cache/CacheModule.ts:

import { ContainerModule, interfaces } from 'inversify';
import { CacheClient, CacheService } from '@hemia/cache-manager';
import type { ICacheClient, ICacheService, RedisConfig } from '@hemia/cache-manager';

export const CACHE_TYPES = {
  RedisConfig: Symbol.for('RedisConfig'),
  CacheClient: Symbol.for('CacheClient'),
  CacheService: Symbol.for('CacheService'),
};

export const createCacheModule = (config: RedisConfig): ContainerModule => {
  return new ContainerModule((bind: interfaces.Bind) => {
    bind<RedisConfig>(CACHE_TYPES.RedisConfig).toConstantValue(config);
    
    bind<ICacheClient>(CACHE_TYPES.CacheClient)
      .toDynamicValue(() => new CacheClient(config))
      .inSingletonScope();
    
    bind<ICacheService>(CACHE_TYPES.CacheService)
      .toDynamicValue((context) => {
        const cacheClient = context.container.get<ICacheClient>(CACHE_TYPES.CacheClient);
        return new CacheService(cacheClient);
      })
      .inSingletonScope();
  });
};

3. Configurar el contenedor

Crea src/infrastructure/inversify.config.ts:

import 'reflect-metadata';
import { Container } from 'inversify';
import { createCacheModule } from './cache/CacheModule';

const container = new Container();

// Cargar el módulo de cache
container.load(
  createCacheModule({
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT || '6379'),
    password: process.env.REDIS_PASSWORD,
    db: 0,
    tls: process.env.REDIS_TLS === 'true',
  })
);

export { container };

4. Usar en tus servicios

import { injectable, inject } from 'inversify';
import type { ICacheService } from '@hemia/cache-manager';
import { CACHE_TYPES } from '../infrastructure/cache/CacheModule';

interface User {
  id: string;
  name: string;
  email: string;
}

@injectable()
export class UserService {
  constructor(
    @inject(CACHE_TYPES.CacheService) private cacheService: ICacheService
  ) {}

  async getUserById(id: string): Promise<User | null> {
    const cacheKey = `user:${id}`;
    
    // Intentar obtener del cache
    const cachedUser = await this.cacheService.getObject<User>(cacheKey);
    if (cachedUser) {
      return cachedUser;
    }

    // Si no está en cache, obtener de la base de datos
    const user = await this.fetchFromDatabase(id);
    
    if (user) {
      // Guardar en cache por 1 hora
      await this.cacheService.setObject(cacheKey, user, 3600);
    }
    
    return user;
  }

  async updateUser(id: string, data: Partial<User>): Promise<User> {
    const user = await this.database.update(id, data);
    
    // Invalidar el cache
    await this.cacheService.deleteKey(`user:${id}`);
    
    return user;
  }
}

API Reference

CacheService

Strings y Números

// Guardar string o número con expiración opcional (en segundos)
await cacheService.setString(key: string, value: string | number, expireTime?: number): Promise<void>

// Obtener string
await cacheService.getString(key: string): Promise<string | null>

Objetos

// Guardar objeto con expiración opcional
await cacheService.setObject<T>(key: string, value: T, expireTime?: number): Promise<void>

// Obtener objeto tipado
await cacheService.getObject<T>(key: string): Promise<T | null>

Listas

// Agregar elemento a una lista (LPUSH)
await cacheService.setList(key: string, value: string | number): Promise<void>

// Obtener elementos de una lista (LRANGE)
await cacheService.getList(key: string, start?: number, stop?: number): Promise<string[]>

Operaciones Generales

// Eliminar una clave
await cacheService.deleteKey(key: string): Promise<void>

// Establecer tiempo de expiración
await cacheService.expire(key: string, seconds: number): Promise<void>

// Verificar si una clave existe
await cacheService.exists(key: string): Promise<boolean>

// Incrementar un contador
await cacheService.increment(key: string): Promise<number>

// Decrementar un contador
await cacheService.decrement(key: string): Promise<number>

// Buscar claves por patrón (usar con precaución en producción)
await cacheService.getKeysByPattern(pattern: string): Promise<string[]>

// Obtener tiempo de vida restante (TTL)
// Retorna: -2 si no existe, -1 si existe sin expiración, segundos restantes
await cacheService.getTTL(key: string): Promise<number>

Conjuntos (Sets)

// Agregar uno o más elementos a un conjunto
await cacheService.addToSet(key: string, ...members: (string | number)[]): Promise<number>

// Eliminar uno o más elementos de un conjunto
await cacheService.removeFromSet(key: string, ...members: (string | number)[]): Promise<number>

// Obtener todos los miembros de un conjunto
await cacheService.getSetMembers(key: string): Promise<string[]>

// Verificar si un elemento pertenece a un conjunto
await cacheService.isSetMember(key: string, member: string | number): Promise<boolean>

// Obtener el número de elementos en un conjunto
await cacheService.getSetSize(key: string): Promise<number>

Transacciones (Multi)

// Ejecutar múltiples comandos de forma atómica
cacheService.multi()
  .set(key1, value1, 'EX', ttl)
  .sadd(key2, member)
  .del(key3)
  .exec();

Ejemplos de Uso

Contador de visitas

async function trackPageView(pageId: string) {
  const key = `page:views:${pageId}`;
  const views = await cacheService.increment(key);
  await cacheService.expire(key, 86400); // Expira en 24 horas
  return views;
}

Cache de sesiones

interface Session {
  userId: string;
  token: string;
  expiresAt: Date;
}

async function saveSession(sessionId: string, session: Session) {
  await cacheService.setObject(`session:${sessionId}`, session, 3600); // 1 hora
}

async function getSession(sessionId: string): Promise<Session | null> {
  return await cacheService.getObject<Session>(`session:${sessionId}`);
}

Cache con patrón Cache-Aside

async function getUser(userId: string) {
  const cacheKey = `user:${userId}`;
  
  // 1. Intentar obtener del cache
  const cached = await cacheService.getObject<User>(cacheKey);
  if (cached) {
    return cached;
  }
  
  // 2. Si no está, obtener de la base de datos
  const user = await database.users.findById(userId);
  
  // 3. Guardar en cache para futuras consultas
  if (user) {
    await cacheService.setObject(cacheKey, user, 1800); // 30 minutos
  }
  
  return user;
}

Invalidación de cache

async function updateUser(userId: string, data: Partial<User>) {
  // Actualizar en la base de datos
  const user = await database.users.update(userId, data);
  
  // Invalidar cache
  await cacheService.deleteKey(`user:${userId}`);
  
  // Invalidar caches relacionados
  await cacheService.deleteKey(`user:profile:${userId}`);
  
  return user;
}

Rate Limiting

async function checkRateLimit(userId: string, maxRequests: number = 100): Promise<boolean> {
  const key = `rate:${userId}`;
  const current = await cacheService.increment(key);
  
  if (current === 1) {
    // Primera request, establecer expiración de 1 minuto
    await cacheService.expire(key, 60);
  }
  
  return current <= maxRequests;
}

Gestión de Sesiones con Sets

interface Session {
  userId: string;
  token: string;
  createdAt: Date;
}

// Crear sesión y agregar al set del usuario
async function createSession(userId: string, sessionId: string, sessionData: Session) {
  const sessionKey = `session:${sessionId}`;
  const userSessionsKey = `user:${userId}:sessions`;
  
  // Usar transacción atómica para garantizar consistencia
  await cacheService.multi()
    .set(sessionKey, JSON.stringify(sessionData), 'EX', 60 * 60 * 24 * 14) // 14 días
    .sadd(userSessionsKey, sessionId)
    .exec();
}

// Eliminar sesión y actualizar el set del usuario
async function deleteSession(sessionId: string) {
  const rawSession = await cacheService.getString(`session:${sessionId}`);
  
  if (rawSession) {
    const { userId } = JSON.parse(rawSession);
    await cacheService.deleteKey(`session:${sessionId}`);
    await cacheService.removeFromSet(`user:${userId}:sessions`, sessionId);
  }
}

// Obtener todas las sesiones activas de un usuario
async function getUserSessions(userId: string): Promise<string[]> {
  return await cacheService.getSetMembers(`user:${userId}:sessions`);
}

// Cerrar todas las sesiones de un usuario
async function revokeAllUserSessions(userId: string) {
  const sessions = await cacheService.getSetMembers(`user:${userId}:sessions`);
  
  for (const sessionId of sessions) {
    await cacheService.deleteKey(`session:${sessionId}`);
  }
  
  await cacheService.deleteKey(`user:${userId}:sessions`);
}

Seguimiento de Usuarios Activos

// Agregar usuario activo
async function markUserActive(userId: string) {
  await cacheService.addToSet('users:active', userId);
  await cacheService.expire('users:active', 3600); // Resetear TTL a 1 hora
}

// Obtener número de usuarios activos
async function getActiveUsersCount(): Promise<number> {
  return await cacheService.getSetSize('users:active');
}

// Verificar si un usuario está activo
async function isUserActive(userId: string): Promise<boolean> {
  return await cacheService.isSetMember('users:active', userId);
}

// Obtener lista de usuarios activos
async function getActiveUsers(): Promise<string[]> {
  return await cacheService.getSetMembers('users:active');
}

Operaciones Atómicas con Multi

// Transferencia de puntos entre usuarios (atómico)
async function transferPoints(fromUserId: string, toUserId: string, points: number) {
  const fromKey = `user:${fromUserId}:points`;
  const toKey = `user:${toUserId}:points`;
  
  await cacheService.multi()
    .decrby(fromKey, points)
    .incrby(toKey, points)
    .exec();
}

// Registrar evento con múltiples operaciones
async function logUserEvent(userId: string, eventType: string) {
  const eventKey = `events:${eventType}`;
  const userEventsKey = `user:${userId}:events`;
  const statsKey = `stats:events:${eventType}`;
  
  await cacheService.multi()
    .lpush(eventKey, userId)
    .sadd(userEventsKey, eventType)
    .incr(statsKey)
    .expire(eventKey, 86400) // 24 horas
    .exec();
}

Configuración de Redis

interface RedisConfig {
  host: string;          // Dirección del servidor Redis
  port: number;          // Puerto (por defecto 6379)
  username?: string;     // Usuario (Redis 6+)
  password?: string;     // Contraseña
  db: number;            // Número de base de datos (0-15)
  tls?: boolean;         // Usar TLS/SSL
}

Ejemplo de configuración completa

const cacheClient = new CacheClient({
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT || '6379'),
  username: process.env.REDIS_USERNAME,
  password: process.env.REDIS_PASSWORD,
  db: parseInt(process.env.REDIS_DB || '0'),
  tls: process.env.NODE_ENV === 'production',
});

TypeScript

La librería está completamente tipada. Puedes usar los tipos e interfaces exportados:

import type { 
  ICacheClient, 
  ICacheService, 
  RedisConfig 
} from '@hemia/cache-manager';

Mejores Prácticas

  1. Usa TTL apropiados: Siempre establece tiempos de expiración para evitar datos obsoletos
  2. Evita KEYS en producción: El método getKeysByPattern usa el comando KEYS que puede ser lento
  3. Implementa Circuit Breaker: Maneja errores de Redis para evitar que afecten tu aplicación
  4. Monitorea el tamaño: Controla el tamaño de los objetos que guardas en cache
  5. Namespace tus claves: Usa prefijos descriptivos como user:, session:, etc.

Licencia

MIT — © Hemia Technologies

Desarrollado por Hemia Technologies