Manejador de caché con soporte Redis y inversify para aplicaciones TypeScript.
npm install @hemia/cache-managerUna 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.
---
- ✨ 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.
---
``bash`
npm install @hemia/cache-manager
---
`ts
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' }
}
`
---
`bash`
npm install inversify reflect-metadata
Crea src/infrastructure/cache/CacheModule.ts:
`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
bind
.toDynamicValue(() => new CacheClient(config))
.inSingletonScope();
bind
.toDynamicValue((context) => {
const cacheClient = context.container.get
return new CacheService(cacheClient);
})
.inSingletonScope();
});
};
`
Crea src/infrastructure/inversify.config.ts:
`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 };
`
`ts
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
const cacheKey = user:${id};
// Intentar obtener del cache
const cachedUser = await this.cacheService.getObject
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
const user = await this.database.update(id, data);
// Invalidar el cache
await this.cacheService.deleteKey(user:${id});`
return user;
}
}
---
#### Strings y Números
`ts
// Guardar string o número con expiración opcional (en segundos)
await cacheService.setString(key: string, value: string | number, expireTime?: number): Promise
// Obtener string
await cacheService.getString(key: string): Promise
`
#### Objetos
`ts
// Guardar objeto con expiración opcional
await cacheService.setObject
// Obtener objeto tipado
await cacheService.getObject
`
#### Listas
`ts
// Agregar elemento a una lista (LPUSH)
await cacheService.setList(key: string, value: string | number): Promise
// Obtener elementos de una lista (LRANGE)
await cacheService.getList(key: string, start?: number, stop?: number): Promise
`
#### Operaciones Generales
`ts
// Eliminar una clave
await cacheService.deleteKey(key: string): Promise
// Establecer tiempo de expiración
await cacheService.expire(key: string, seconds: number): Promise
// Verificar si una clave existe
await cacheService.exists(key: string): Promise
// Incrementar un contador
await cacheService.increment(key: string): Promise
// Decrementar un contador
await cacheService.decrement(key: string): Promise
// Buscar claves por patrón (usar con precaución en producción)
await cacheService.getKeysByPattern(pattern: string): Promise
// 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
`
#### Conjuntos (Sets)
`ts
// Agregar uno o más elementos a un conjunto
await cacheService.addToSet(key: string, ...members: (string | number)[]): Promise
// Eliminar uno o más elementos de un conjunto
await cacheService.removeFromSet(key: string, ...members: (string | number)[]): Promise
// Obtener todos los miembros de un conjunto
await cacheService.getSetMembers(key: string): Promise
// Verificar si un elemento pertenece a un conjunto
await cacheService.isSetMember(key: string, member: string | number): Promise
// Obtener el número de elementos en un conjunto
await cacheService.getSetSize(key: string): Promise
`
#### Transacciones (Multi)
`ts`
// Ejecutar múltiples comandos de forma atómica
cacheService.multi()
.set(key1, value1, 'EX', ttl)
.sadd(key2, member)
.del(key3)
.exec();
---
`tspage:views:${pageId}
async function trackPageView(pageId: string) {
const key = ;`
const views = await cacheService.increment(key);
await cacheService.expire(key, 86400); // Expira en 24 horas
return views;
}
`ts
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
return await cacheService.getObject);`
}
`tsuser:${userId}
async function getUser(userId: string) {
const cacheKey = ;`
// 1. Intentar obtener del cache
const cached = await cacheService.getObject
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;
}
`tsuser:${userId}
async function updateUser(userId: string, data: Partial
// Actualizar en la base de datos
const user = await database.users.update(userId, data);
// Invalidar cache
await cacheService.deleteKey();user:profile:${userId}
// Invalidar caches relacionados
await cacheService.deleteKey();`
return user;
}
`tsrate:${userId}
async function checkRateLimit(userId: string, maxRequests: number = 100): Promise
const key = ;`
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;
}
`ts
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};user:${userId}:sessions
const userSessionsKey = ;
// 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});session:${sessionId}
if (rawSession) {
const { userId } = JSON.parse(rawSession);
await cacheService.deleteKey();user:${userId}:sessions
await cacheService.removeFromSet(, sessionId);
}
}
// Obtener todas las sesiones activas de un usuario
async function getUserSessions(userId: string): Promise
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);session:${sessionId}
for (const sessionId of sessions) {
await cacheService.deleteKey();user:${userId}:sessions
}
await cacheService.deleteKey();`
}
`ts
// 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
return await cacheService.getSetSize('users:active');
}
// Verificar si un usuario está activo
async function isUserActive(userId: string): Promise
return await cacheService.isSetMember('users:active', userId);
}
// Obtener lista de usuarios activos
async function getActiveUsers(): Promise
return await cacheService.getSetMembers('users:active');
}
`
`tsuser:${fromUserId}:points
// Transferencia de puntos entre usuarios (atómico)
async function transferPoints(fromUserId: string, toUserId: string, points: number) {
const fromKey = ;user:${toUserId}:points
const toKey = ;
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};user:${userId}:events
const userEventsKey = ;stats:events:${eventType}
const statsKey = ;`
await cacheService.multi()
.lpush(eventKey, userId)
.sadd(userEventsKey, eventType)
.incr(statsKey)
.expire(eventKey, 86400) // 24 horas
.exec();
}
---
`ts`
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
}
`ts`
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',
});
---
La librería está completamente tipada. Puedes usar los tipos e interfaces exportados:
`ts`
import type {
ICacheClient,
ICacheService,
RedisConfig
} from '@hemia/cache-manager';
---
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 lentouser:
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 , session:`, etc.
---
MIT — © Hemia Technologies
Desarrollado por Hemia Technologies