Warnings for rate limits and quota violations tend to occur repeatedly in short time spans. By embedding warning logic and cooldown state within the entity itself, you can avoid external global management while ensuring warnings are emitted only at appropriate intervals.
Code
interface WarnableEntity { /** Timestamp of the last warning (milliseconds) */ warnedAt: number | null /** Warning cooldown period (milliseconds) */ warnCooldown: number}
/** * Base class for entities with cooldown-based warnings */class CooldownWarner<T extends WarnableEntity> { constructor(protected entity: T) {}
/** * Determine if a warning should be emitted */ protected shouldWarn(): boolean { const now = Date.now() if (this.entity.warnedAt === null) { return true } const elapsed = now - this.entity.warnedAt return elapsed >= this.entity.warnCooldown }
/** * Emit a warning and update warnedAt */ protected warnBy(message: string): void { if (!this.shouldWarn()) { return } console.warn(message) this.entity.warnedAt = Date.now() }}
/** * Example implementation: rate limit entity */interface RateLimitEntity extends WarnableEntity { limit: number current: number}
class RateLimiter extends CooldownWarner<RateLimitEntity> { constructor(limit: number, cooldown: number = 60_000) { super({ limit, current: 0, warnedAt: null, warnCooldown: cooldown }) }
/** * Record a request and warn if necessary */ record(): void { this.entity.current++
if (this.entity.current >= this.entity.limit) { this.warnBy( `Rate limit reached: ${this.entity.current}/${this.entity.limit}` ) } }
/** * Reset the counter */ reset(): void { this.entity.current = 0 }}Usage
// 100 requests/min rate limit, warn at most once per 60 secondsconst limiter = new RateLimiter(100, 60_000)
// Record requestsfor (let i = 0; i < 150; i++) { limiter.record() // Warns at the 100th request, suppressed for 60s after that}
// After 1 minute, exceeding again will trigger another warningsetTimeout(() => { limiter.record() // Warning is displayed again}, 61_000)How It Works
- The entity has
warnedAtandwarnCooldownfields shouldWarn()compares the current time with the last warning timestamp- Returns
trueif the cooldown period has elapsed warnBy()checksshouldWarn()and emits a warning only if the condition is met- After warning, updates
warnedAtto start the cooldown
Since the warning logic and state are encapsulated within the entity, there’s no need for global maps or sets.
Benefits
- Locality: Warning state is self-contained within the entity, no external management needed
- Reusability: Any entity can implement
WarnableEntityto use this pattern - Testable: Inject time for testing without mocks
- Scalable: Each entity maintains independent state, friendly to concurrent processing
Caveats
Direct dependency on Date.now() means you’ll need to extend the design to inject time for testing. Also, if entities are persisted, ensure proper restoration of warnedAt and cooldown logic.
Applications
- Warning notifications for API rate limits
- Quota violation alerts in billing systems
- Low-balance notifications
- Suppression of noisy monitoring alerts
- Burst prevention in logging
hsb.horse