Lesson 16 +10 XP

Project: Type-Safe Generic Cache & Event Emitter

Project 2: Type-Safe Generic Cache & Event Emitter

Build a generic memory cache with Time-To-Live (TTL) expiration and a fully typed Event Emitter library.

Typed Event Emitter Implementation

export type EventMap = Record<string, any>;

export class TypedEventEmitter<Events extends EventMap> {
  private listeners: { [K in keyof Events]?: Array<(data: Events[K]) => void> } = {};

  on<K extends keyof Events>(event: K, listener: (data: Events[K]) => void): void {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event]!.push(listener);
  }

  emit<K extends keyof Events>(event: K, data: Events[K]): void {
    const handlers = this.listeners[event];
    if (handlers) {
      handlers.forEach((fn) => fn(data));
    }
  }
}

Generic Cache Implementation

interface CacheEntry<V> {
  value: V;
  expiresAt: number;
}

export class MemoryCache<K extends string | number, V> {
  private store = new Map<K, CacheEntry<V>>();

  set(key: K, value: V, ttlMs: number): void {
    const expiresAt = Date.now() + ttlMs;
    this.store.set(key, { value, expiresAt });
  }

  get(key: K): V | undefined {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (Date.now() > entry.expiresAt) {
      this.store.delete(key);
      return undefined;
    }
    return entry.value;
  }
}

Usage Example

interface MyEvents {
  userLoggedIn: { userId: string; timestamp: number };
  cacheCleared: void;
}

const emitter = new TypedEventEmitter<MyEvents>();
emitter.on("userLoggedIn", (data) => {
  console.log(`User ${data.userId} logged in at ${data.timestamp}`);
});

TL;DR

  • Demonstrates mapped types for strongly typed event maps.
  • Combines generic key/value constraints with runtime expiration logic.