workspace-v1/shared/catalog/rate-limiting/_reference/rate-limiter.service.reference.ts
rckrdmrd cb4c0681d3 feat(workspace): Add new projects and update architecture
New projects created:
- michangarrito (marketplace mobile)
- template-saas (SaaS template)
- clinica-dental (dental ERP)
- clinica-veterinaria (veterinary ERP)

Architecture updates:
- Move catalog from core/ to shared/
- Add MCP servers structure and templates
- Add git management scripts
- Update SUBREPOSITORIOS.md with 15 new repos
- Update .gitignore for new projects

Repository infrastructure:
- 4 main repositories
- 11 subrepositorios
- Gitea remotes configured

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:43:28 -06:00

187 lines
4.4 KiB
TypeScript

/**
* RATE LIMITER SERVICE - REFERENCE IMPLEMENTATION
*
* @description Servicio de rate limiting para proteger endpoints.
* Implementación in-memory simple con soporte para diferentes estrategias.
*
* @usage
* ```typescript
* // En middleware o guard
* const limiter = getRateLimiter({ windowMs: 60000, max: 100 });
* if (limiter.isRateLimited(req.ip)) {
* throw new TooManyRequestsException();
* }
* ```
*
* @origin gamilit/apps/backend/src/shared/services/rate-limiter.service.ts
*/
/**
* Configuración del rate limiter
*/
export interface RateLimitConfig {
/** Ventana de tiempo en milisegundos */
windowMs: number;
/** Máximo de requests por ventana */
max: number;
/** Mensaje de error personalizado */
message?: string;
/** Función para generar key (default: IP) */
keyGenerator?: (req: any) => string;
}
/**
* Estado interno de un cliente
*/
interface ClientState {
count: number;
resetTime: number;
}
/**
* Rate Limiter Factory
*
* @param config - Configuración del limiter
* @returns Instancia del rate limiter
*/
export function getRateLimiter(config: RateLimitConfig): RateLimiter {
const clients = new Map<string, ClientState>();
// Limpieza periódica de entradas expiradas
const cleanup = setInterval(() => {
const now = Date.now();
for (const [key, state] of clients.entries()) {
if (state.resetTime <= now) {
clients.delete(key);
}
}
}, config.windowMs);
return {
/**
* Verificar si un cliente está rate limited
*/
check(key: string): RateLimitResult {
const now = Date.now();
const state = clients.get(key);
// Cliente nuevo o ventana expirada
if (!state || state.resetTime <= now) {
clients.set(key, {
count: 1,
resetTime: now + config.windowMs,
});
return {
limited: false,
remaining: config.max - 1,
resetTime: now + config.windowMs,
};
}
// Incrementar contador
state.count++;
// Verificar límite
if (state.count > config.max) {
return {
limited: true,
remaining: 0,
resetTime: state.resetTime,
retryAfter: Math.ceil((state.resetTime - now) / 1000),
};
}
return {
limited: false,
remaining: config.max - state.count,
resetTime: state.resetTime,
};
},
/**
* Resetear contador de un cliente
*/
reset(key: string): void {
clients.delete(key);
},
/**
* Limpiar todos los contadores
*/
clear(): void {
clients.clear();
},
/**
* Destruir el limiter (detener cleanup)
*/
destroy(): void {
clearInterval(cleanup);
clients.clear();
},
};
}
/**
* Interfaz del rate limiter
*/
export interface RateLimiter {
check(key: string): RateLimitResult;
reset(key: string): void;
clear(): void;
destroy(): void;
}
/**
* Resultado de verificación
*/
export interface RateLimitResult {
limited: boolean;
remaining: number;
resetTime: number;
retryAfter?: number;
}
/**
* Middleware para Express/NestJS
*/
export function rateLimitMiddleware(config: RateLimitConfig) {
const limiter = getRateLimiter(config);
const keyGenerator = config.keyGenerator || ((req) => req.ip || 'unknown');
return (req: any, res: any, next: () => void) => {
const key = keyGenerator(req);
const result = limiter.check(key);
// Agregar headers informativos
res.setHeader('X-RateLimit-Limit', config.max);
res.setHeader('X-RateLimit-Remaining', result.remaining);
res.setHeader('X-RateLimit-Reset', result.resetTime);
if (result.limited) {
res.setHeader('Retry-After', result.retryAfter);
res.status(429).json({
statusCode: 429,
message: config.message || 'Too many requests',
retryAfter: result.retryAfter,
});
return;
}
next();
};
}
/**
* Error de rate limit
*/
export class TooManyRequestsError extends Error {
public readonly statusCode = 429;
public readonly retryAfter: number;
constructor(retryAfter: number, message?: string) {
super(message || 'Too many requests');
this.retryAfter = retryAfter;
}
}