+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 60 of 354

🧩 Multiple Type Parameters: Complex Generic Types

Master multiple type parameters in TypeScript to create sophisticated generic patterns and type relationships 🚀

💎Advanced
35 min read

Prerequisites

  • Strong understanding of TypeScript generics 📝
  • Knowledge of generic functions and classes 🔍
  • Familiarity with type constraints 💻

What you'll learn

  • Create complex multi-parameter generic types 🎯
  • Build sophisticated type relationships 🏗️
  • Master type parameter constraints and inference 🛡️
  • Design professional generic libraries ✨

🎯 Introduction

Welcome to the advanced world of multiple type parameters! 🎉 In this guide, we’ll explore how to create sophisticated generic types that work with multiple type parameters to build complex, flexible, and type-safe systems.

You’ll discover how multiple type parameters are like multi-dimensional building blocks 🧩 - they allow you to create intricate type relationships and patterns! Whether you’re building advanced utilities 🔧, designing complex APIs 🌐, or creating professional libraries 📚, mastering multiple type parameters is essential for advanced TypeScript development.

By the end of this tutorial, you’ll be confidently creating complex generic types that elegantly solve real-world problems! Let’s dive into advanced generic patterns! 🏊‍♂️

📚 Understanding Multiple Type Parameters

🤔 Beyond Single Type Parameters

Multiple type parameters enable you to create sophisticated relationships between types:

// ❌ Limited with single type parameter
interface SimpleMap<T> {
  get(key: string): T;
  set(key: string, value: T): void;
}
// Can only handle string keys! 😢

// ✅ Flexible with multiple type parameters
interface FlexibleMap<K, V> {
  get(key: K): V | undefined;
  set(key: K, value: V): void;
  has(key: K): boolean;
  delete(key: K): boolean;
}

// Now works with any key and value types!
const numberMap: FlexibleMap<number, string> = new Map();
const symbolMap: FlexibleMap<symbol, User> = new Map();
const objectMap: FlexibleMap<{ id: string }, boolean> = new Map();

// 🎯 Type parameters can reference each other
interface Converter<TInput, TOutput> {
  convert(input: TInput): TOutput;
  convertMany(inputs: TInput[]): TOutput[];
  canConvert(value: unknown): value is TInput;
}

// 🏗️ Complex relationships between parameters
interface Relationship<TParent, TChild> {
  parent: TParent;
  children: TChild[];
  addChild(child: TChild): void;
  removeChild(child: TChild): boolean;
  findChild(predicate: (child: TChild) => boolean): TChild | undefined;
}

💡 Type Parameter Ordering and Naming

Best practices for multiple type parameters:

// 🎯 Conventional ordering and naming
// 1. Input types before output types
// 2. More general types before specific types
// 3. Meaningful names for clarity

interface Transform<TInput, TOutput, TOptions = {}> {
  transform(input: TInput, options?: TOptions): TOutput;
}

interface AsyncProcessor<
  TRequest,
  TResponse,
  TError = Error,
  TContext = {}
> {
  process(
    request: TRequest,
    context?: TContext
  ): Promise<TResponse>;
  
  handleError?(
    error: TError,
    request: TRequest,
    context?: TContext
  ): TResponse | Promise<TResponse>;
}

// 🏗️ Descriptive names for complex scenarios
interface DataMigration<
  TSourceSchema,
  TTargetSchema,
  TMigrationContext = {}
> {
  migrate(
    source: TSourceSchema,
    context?: TMigrationContext
  ): TTargetSchema;
  
  rollback(
    target: TTargetSchema,
    context?: TMigrationContext
  ): TSourceSchema;
}

// 🔧 Default type parameters
interface Cache<
  TKey = string,
  TValue = any,
  TMetadata = { timestamp: Date }
> {
  get(key: TKey): TValue | undefined;
  set(key: TKey, value: TValue, metadata?: TMetadata): void;
  getMetadata(key: TKey): TMetadata | undefined;
}

// Using defaults
const simpleCache: Cache = new CacheImpl(); // Uses all defaults
const customCache: Cache<number, User> = new CacheImpl(); // Override some

🚀 Advanced Constraint Patterns

🔗 Interdependent Constraints

Creating complex relationships between type parameters:

// 🎯 Constraints that reference other type parameters
interface Mapper<T, U extends keyof T, V> {
  map(obj: T, key: U, value: V): T & { [K in U]: V };
}

// 🏗️ Nested constraint relationships
interface NestedAccessor<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1]
> {
  get(obj: T, key1: K1, key2: K2): T[K1][K2];
  set(obj: T, key1: K1, key2: K2, value: T[K1][K2]): void;
}

// Usage
interface Person {
  name: string;
  address: {
    street: string;
    city: string;
    country: string;
  };
  contacts: {
    email: string;
    phone: string;
  };
}

const accessor: NestedAccessor<Person, 'address', 'city'> = {
  get(person, key1, key2) {
    return person[key1][key2];
  },
  set(person, key1, key2, value) {
    person[key1][key2] = value;
  }
};

// 🔧 Conditional constraints
type KeysOfType<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never;
}[keyof T];

interface TypedSelector<T, U> {
  select<K extends KeysOfType<T, U>>(
    obj: T,
    key: K
  ): T[K];
  
  selectAll<K extends KeysOfType<T, U>>(
    obj: T
  ): Pick<T, K>;
}

// 🎨 Complex conditional relationships
interface ConditionalTransform<T, U, V> {
  transform: T extends U ? (value: T) => V : never;
  canTransform: (value: unknown) => value is T;
  reverseTransform?: V extends T ? (value: V) => T : never;
}

🎭 Higher-Order Type Parameters

Working with generic types that accept generic types:

// 🎯 Generic type that works with other generics
interface Container<T> {
  value: T;
}

interface Processor<
  TContainer extends Container<any>,
  TResult
> {
  process<T>(
    container: TContainer & Container<T>
  ): TResult;
}

// 🏗️ Type parameter extraction
type ExtractContainerType<T> = T extends Container<infer U> ? U : never;

interface UnwrapProcessor<
  TContainer extends Container<any>
> {
  unwrap(container: TContainer): ExtractContainerType<TContainer>;
  wrap<T>(value: T): Container<T>;
}

// 🔧 Generic function signatures as parameters
interface Pipeline<
  TFn1 extends (...args: any[]) => any,
  TFn2 extends (arg: ReturnType<TFn1>) => any,
  TFn3 extends (arg: ReturnType<TFn2>) => any = never
> {
  pipe: TFn3 extends never
    ? (fn1: TFn1, fn2: TFn2) => (...args: Parameters<TFn1>) => ReturnType<TFn2>
    : (fn1: TFn1, fn2: TFn2, fn3: TFn3) => (...args: Parameters<TFn1>) => ReturnType<TFn3>;
}

// 🎨 Recursive type parameters
interface TreeNode<TValue, TChildren extends TreeNode<any, any> = TreeNode<TValue, any>> {
  value: TValue;
  children: TChildren[];
}

interface TreeProcessor<
  TNode extends TreeNode<any, any>,
  TValue = TNode extends TreeNode<infer V, any> ? V : never
> {
  traverse(root: TNode, visitor: (value: TValue) => void): void;
  find(root: TNode, predicate: (value: TValue) => boolean): TNode | null;
  map<TNewValue>(
    root: TNode,
    transform: (value: TValue) => TNewValue
  ): TreeNode<TNewValue>;
}

🎪 Real-World Patterns

🔄 Generic State Machines

Building type-safe state machines with multiple parameters:

// 🎯 State machine with states, events, and context
interface StateMachine<
  TState extends string,
  TEvent extends { type: string },
  TContext = {}
> {
  currentState: TState;
  context: TContext;
  
  transition(event: TEvent): void;
  canTransition(event: TEvent): boolean;
  onStateChange(listener: (newState: TState, oldState: TState) => void): void;
}

// 🏗️ State configuration
interface StateConfig<
  TState extends string,
  TEvent extends { type: string },
  TContext
> {
  initial: TState;
  context: TContext;
  states: {
    [K in TState]: {
      on?: {
        [E in TEvent['type']]?: {
          target: TState;
          guard?: (context: TContext, event: TEvent) => boolean;
          action?: (context: TContext, event: TEvent) => void;
        };
      };
      entry?: (context: TContext) => void;
      exit?: (context: TContext) => void;
    };
  };
}

// Implementation
class StateMachineImpl<
  TState extends string,
  TEvent extends { type: string },
  TContext
> implements StateMachine<TState, TEvent, TContext> {
  currentState: TState;
  context: TContext;
  private listeners: Array<(newState: TState, oldState: TState) => void> = [];
  
  constructor(private config: StateConfig<TState, TEvent, TContext>) {
    this.currentState = config.initial;
    this.context = config.context;
    this.enterState(this.currentState);
  }
  
  transition(event: TEvent): void {
    const stateConfig = this.config.states[this.currentState];
    const transition = stateConfig.on?.[event.type];
    
    if (!transition) return;
    
    if (transition.guard && !transition.guard(this.context, event)) {
      return;
    }
    
    const oldState = this.currentState;
    
    // Exit current state
    this.exitState(oldState);
    
    // Execute action
    transition.action?.(this.context, event);
    
    // Enter new state
    this.currentState = transition.target;
    this.enterState(this.currentState);
    
    // Notify listeners
    this.notifyListeners(this.currentState, oldState);
  }
  
  canTransition(event: TEvent): boolean {
    const stateConfig = this.config.states[this.currentState];
    const transition = stateConfig.on?.[event.type];
    
    if (!transition) return false;
    
    return !transition.guard || transition.guard(this.context, event);
  }
  
  onStateChange(listener: (newState: TState, oldState: TState) => void): void {
    this.listeners.push(listener);
  }
  
  private enterState(state: TState): void {
    this.config.states[state].entry?.(this.context);
  }
  
  private exitState(state: TState): void {
    this.config.states[state].exit?.(this.context);
  }
  
  private notifyListeners(newState: TState, oldState: TState): void {
    this.listeners.forEach(listener => listener(newState, oldState));
  }
}

// Usage example
type TrafficLightState = 'red' | 'yellow' | 'green';
type TrafficLightEvent = 
  | { type: 'TIMER' }
  | { type: 'EMERGENCY' }
  | { type: 'RESET' };

interface TrafficLightContext {
  emergencyMode: boolean;
  cycleCount: number;
}

const trafficLight = new StateMachineImpl<
  TrafficLightState,
  TrafficLightEvent,
  TrafficLightContext
>({
  initial: 'red',
  context: { emergencyMode: false, cycleCount: 0 },
  states: {
    red: {
      on: {
        TIMER: {
          target: 'green',
          guard: (ctx) => !ctx.emergencyMode,
          action: (ctx) => ctx.cycleCount++
        },
        EMERGENCY: {
          target: 'red',
          action: (ctx) => ctx.emergencyMode = true
        }
      },
      entry: () => console.log('🔴 Red light')
    },
    yellow: {
      on: {
        TIMER: { target: 'red' },
        EMERGENCY: {
          target: 'red',
          action: (ctx) => ctx.emergencyMode = true
        }
      },
      entry: () => console.log('🟡 Yellow light')
    },
    green: {
      on: {
        TIMER: { target: 'yellow' },
        EMERGENCY: {
          target: 'red',
          action: (ctx) => ctx.emergencyMode = true
        }
      },
      entry: () => console.log('🟢 Green light')
    }
  }
});

🗺️ Generic Data Mappers

Complex data transformation with multiple type parameters:

// 🎯 Bidirectional mapper
interface BidirectionalMapper<TSource, TTarget, TContext = {}> {
  map(source: TSource, context?: TContext): TTarget;
  reverseMap(target: TTarget, context?: TContext): TSource;
  mapMany(sources: TSource[], context?: TContext): TTarget[];
  reversemapMany(targets: TTarget[], context?: TContext): TSource[];
}

// 🏗️ Field mapping configuration
interface FieldMapping<
  TSource,
  TTarget,
  TSourceField extends keyof TSource,
  TTargetField extends keyof TTarget
> {
  from: TSourceField;
  to: TTargetField;
  transform?: (value: TSource[TSourceField]) => TTarget[TTargetField];
  reverseTransform?: (value: TTarget[TTargetField]) => TSource[TSourceField];
  condition?: (source: TSource) => boolean;
}

// 🔧 Mapper builder
class MapperBuilder<TSource, TTarget, TContext = {}> {
  private fieldMappings: Array<FieldMapping<TSource, TTarget, any, any>> = [];
  private customMappers: Array<(source: TSource, target: Partial<TTarget>, context?: TContext) => void> = [];
  
  field<
    TSourceField extends keyof TSource,
    TTargetField extends keyof TTarget
  >(
    from: TSourceField,
    to: TTargetField,
    transform?: (value: TSource[TSourceField]) => TTarget[TTargetField]
  ): this {
    this.fieldMappings.push({ from, to, transform });
    return this;
  }
  
  custom(
    mapper: (source: TSource, target: Partial<TTarget>, context?: TContext) => void
  ): this {
    this.customMappers.push(mapper);
    return this;
  }
  
  build(): BidirectionalMapper<TSource, TTarget, TContext> {
    return new MapperImpl(this.fieldMappings, this.customMappers);
  }
}

class MapperImpl<TSource, TTarget, TContext>
  implements BidirectionalMapper<TSource, TTarget, TContext> {
  constructor(
    private fieldMappings: Array<FieldMapping<TSource, TTarget, any, any>>,
    private customMappers: Array<(source: TSource, target: Partial<TTarget>, context?: TContext) => void>
  ) {}
  
  map(source: TSource, context?: TContext): TTarget {
    const target: Partial<TTarget> = {};
    
    // Apply field mappings
    for (const mapping of this.fieldMappings) {
      if (mapping.condition && !mapping.condition(source)) {
        continue;
      }
      
      const value = source[mapping.from];
      target[mapping.to] = mapping.transform
        ? mapping.transform(value)
        : value as any;
    }
    
    // Apply custom mappers
    for (const mapper of this.customMappers) {
      mapper(source, target, context);
    }
    
    return target as TTarget;
  }
  
  reverseMap(target: TTarget, context?: TContext): TSource {
    // Implementation for reverse mapping
    throw new Error('Reverse mapping not implemented');
  }
  
  mapMany(sources: TSource[], context?: TContext): TTarget[] {
    return sources.map(source => this.map(source, context));
  }
  
  reversemapMany(targets: TTarget[], context?: TContext): TSource[] {
    return targets.map(target => this.reverseMap(target, context));
  }
}

// Usage
interface UserEntity {
  id: number;
  firstName: string;
  lastName: string;
  emailAddress: string;
  dateOfBirth: Date;
  isActive: boolean;
}

interface UserDTO {
  id: string;
  fullName: string;
  email: string;
  age: number;
  status: 'active' | 'inactive';
}

const userMapper = new MapperBuilder<UserEntity, UserDTO>()
  .field('id', 'id', (id) => id.toString())
  .field('emailAddress', 'email')
  .field('isActive', 'status', (active) => active ? 'active' : 'inactive')
  .custom((source, target) => {
    target.fullName = `${source.firstName} ${source.lastName}`;
    target.age = Math.floor(
      (Date.now() - source.dateOfBirth.getTime()) / (365.25 * 24 * 60 * 60 * 1000)
    );
  })
  .build();

🔐 Type-Safe Query Builders

Creating complex query builders with multiple type parameters:

// 🎯 Query builder with table, select, and where types
interface QueryBuilder<
  TTable extends Record<string, any>,
  TSelect extends keyof TTable = keyof TTable,
  TWhere extends Partial<TTable> = Partial<TTable>
> {
  select<K extends keyof TTable>(
    ...fields: K[]
  ): QueryBuilder<TTable, K, TWhere>;
  
  where<K extends keyof TTable>(
    field: K,
    value: TTable[K]
  ): QueryBuilder<TTable, TSelect, TWhere & Record<K, TTable[K]>>;
  
  orderBy<K extends keyof TTable>(
    field: K,
    direction?: 'asc' | 'desc'
  ): QueryBuilder<TTable, TSelect, TWhere>;
  
  limit(count: number): QueryBuilder<TTable, TSelect, TWhere>;
  
  execute(): Promise<Pick<TTable, TSelect>[]>;
}

// 🏗️ Advanced query operations
interface AdvancedQueryBuilder<
  TTable extends Record<string, any>,
  TJoined extends Record<string, any> = {},
  TSelect extends keyof (TTable & TJoined) = keyof (TTable & TJoined)
> {
  join<
    TJoinTable extends Record<string, any>,
    TOn extends keyof TTable & keyof TJoinTable
  >(
    table: string,
    on: { left: TOn; right: TOn }
  ): AdvancedQueryBuilder<TTable, TJoined & TJoinTable, TSelect>;
  
  groupBy<K extends TSelect>(
    ...fields: K[]
  ): GroupedQueryBuilder<TTable & TJoined, K>;
  
  having<K extends TSelect>(
    field: K,
    condition: (value: (TTable & TJoined)[K]) => boolean
  ): AdvancedQueryBuilder<TTable, TJoined, TSelect>;
}

interface GroupedQueryBuilder<
  TTable extends Record<string, any>,
  TGroupBy extends keyof TTable
> {
  aggregate<TAgg extends Record<string, any>>(
    aggregations: {
      [K in keyof TAgg]: {
        field: keyof TTable;
        function: 'sum' | 'avg' | 'count' | 'min' | 'max';
      };
    }
  ): Promise<Array<Pick<TTable, TGroupBy> & TAgg>>;
}

// 🔧 Implementation
class QueryBuilderImpl<
  TTable extends Record<string, any>,
  TSelect extends keyof TTable = keyof TTable,
  TWhere extends Partial<TTable> = Partial<TTable>
> implements QueryBuilder<TTable, TSelect, TWhere> {
  private selectedFields: TSelect[] = [];
  private whereConditions: Partial<TTable> = {};
  private orderByField?: keyof TTable;
  private orderDirection: 'asc' | 'desc' = 'asc';
  private limitCount?: number;
  
  constructor(private tableName: string) {}
  
  select<K extends keyof TTable>(
    ...fields: K[]
  ): QueryBuilder<TTable, K, TWhere> {
    return new QueryBuilderImpl<TTable, K, TWhere>(this.tableName);
  }
  
  where<K extends keyof TTable>(
    field: K,
    value: TTable[K]
  ): QueryBuilder<TTable, TSelect, TWhere & Record<K, TTable[K]>> {
    this.whereConditions[field] = value;
    return this as any;
  }
  
  orderBy<K extends keyof TTable>(
    field: K,
    direction: 'asc' | 'desc' = 'asc'
  ): QueryBuilder<TTable, TSelect, TWhere> {
    this.orderByField = field;
    this.orderDirection = direction;
    return this;
  }
  
  limit(count: number): QueryBuilder<TTable, TSelect, TWhere> {
    this.limitCount = count;
    return this;
  }
  
  async execute(): Promise<Pick<TTable, TSelect>[]> {
    // Mock implementation
    console.log('Executing query:', {
      table: this.tableName,
      select: this.selectedFields,
      where: this.whereConditions,
      orderBy: this.orderByField,
      limit: this.limitCount
    });
    return [];
  }
}

// Usage
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
  createdAt: Date;
}

const query = new QueryBuilderImpl<User>('users')
  .select('id', 'name', 'email')
  .where('isActive', true)
  .where('age', 25)
  .orderBy('createdAt', 'desc')
  .limit(10);

// Type of result is Pick<User, 'id' | 'name' | 'email'>[]
const users = await query.execute();

🎮 Hands-On Exercise

Let’s build a type-safe event-driven system with multiple type parameters!

📝 Challenge: Advanced Event System

Create an event system that:

  1. Supports typed events with payloads
  2. Provides middleware/interceptors
  3. Includes event replay functionality
  4. Has typed event handlers with context
// Your challenge: Implement this advanced event system
interface EventConfig<TEvent, TPayload, TContext = {}> {
  name: TEvent;
  validator?: (payload: TPayload) => boolean;
  transformer?: (payload: TPayload, context: TContext) => TPayload;
}

interface EventBus<
  TEventMap extends Record<string, any>,
  TContext = {}
> {
  on<K extends keyof TEventMap>(
    event: K,
    handler: (payload: TEventMap[K], context: TContext) => void | Promise<void>
  ): () => void;
  
  emit<K extends keyof TEventMap>(
    event: K,
    payload: TEventMap[K]
  ): Promise<void>;
  
  intercept<K extends keyof TEventMap>(
    event: K,
    interceptor: (
      payload: TEventMap[K],
      next: () => Promise<void>
    ) => Promise<void>
  ): () => void;
  
  replay<K extends keyof TEventMap>(
    event: K,
    filter?: (payload: TEventMap[K], timestamp: Date) => boolean
  ): Promise<void>;
}

// Example usage to support:
interface AppEvents {
  userLogin: { userId: string; timestamp: Date };
  userLogout: { userId: string; reason?: string };
  dataUpdate: { table: string; id: string; changes: any };
  error: { code: string; message: string; stack?: string };
}

interface AppContext {
  requestId: string;
  userId?: string;
  permissions: string[];
}

const eventBus = createEventBus<AppEvents, AppContext>();

eventBus.on('userLogin', async (payload, context) => {
  console.log(`User ${payload.userId} logged in`);
});

eventBus.intercept('dataUpdate', async (payload, next) => {
  console.log('Before update:', payload);
  await next();
  console.log('After update');
});

💡 Solution

Click to see the solution
// 🎯 Complete advanced event system implementation
interface EventConfig<TEvent, TPayload, TContext = {}> {
  name: TEvent;
  validator?: (payload: TPayload) => boolean;
  transformer?: (payload: TPayload, context: TContext) => TPayload;
  metadata?: Record<string, any>;
}

interface EventRecord<TPayload> {
  payload: TPayload;
  timestamp: Date;
  context?: any;
  metadata?: Record<string, any>;
}

interface EventBus<
  TEventMap extends Record<string, any>,
  TContext = {}
> {
  on<K extends keyof TEventMap>(
    event: K,
    handler: (payload: TEventMap[K], context: TContext) => void | Promise<void>
  ): () => void;
  
  once<K extends keyof TEventMap>(
    event: K,
    handler: (payload: TEventMap[K], context: TContext) => void | Promise<void>
  ): () => void;
  
  emit<K extends keyof TEventMap>(
    event: K,
    payload: TEventMap[K],
    context?: TContext
  ): Promise<void>;
  
  intercept<K extends keyof TEventMap>(
    event: K,
    interceptor: (
      payload: TEventMap[K],
      context: TContext,
      next: () => Promise<void>
    ) => Promise<void>
  ): () => void;
  
  replay<K extends keyof TEventMap>(
    event: K,
    filter?: (payload: TEventMap[K], timestamp: Date) => boolean
  ): Promise<void>;
  
  getHistory<K extends keyof TEventMap>(
    event?: K
  ): EventRecord<K extends keyof TEventMap ? TEventMap[K] : any>[];
  
  clear(event?: keyof TEventMap): void;
  
  configure<K extends keyof TEventMap>(
    config: EventConfig<K, TEventMap[K], TContext>
  ): void;
}

// 🏗️ Advanced implementation
class AdvancedEventBus<
  TEventMap extends Record<string, any>,
  TContext = {}
> implements EventBus<TEventMap, TContext> {
  private handlers = new Map<keyof TEventMap, Set<Function>>();
  private onceHandlers = new Map<keyof TEventMap, Set<Function>>();
  private interceptors = new Map<keyof TEventMap, Array<Function>>();
  private history = new Map<keyof TEventMap, EventRecord<any>[]>();
  private configs = new Map<keyof TEventMap, EventConfig<any, any, TContext>>();
  private defaultContext: TContext;
  
  constructor(defaultContext: TContext) {
    this.defaultContext = defaultContext;
  }
  
  on<K extends keyof TEventMap>(
    event: K,
    handler: (payload: TEventMap[K], context: TContext) => void | Promise<void>
  ): () => void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    
    this.handlers.get(event)!.add(handler);
    
    return () => {
      this.handlers.get(event)?.delete(handler);
    };
  }
  
  once<K extends keyof TEventMap>(
    event: K,
    handler: (payload: TEventMap[K], context: TContext) => void | Promise<void>
  ): () => void {
    if (!this.onceHandlers.has(event)) {
      this.onceHandlers.set(event, new Set());
    }
    
    this.onceHandlers.get(event)!.add(handler);
    
    return () => {
      this.onceHandlers.get(event)?.delete(handler);
    };
  }
  
  async emit<K extends keyof TEventMap>(
    event: K,
    payload: TEventMap[K],
    context?: TContext
  ): Promise<void> {
    const effectiveContext = context || this.defaultContext;
    const config = this.configs.get(event);
    
    // Validate payload
    if (config?.validator && !config.validator(payload)) {
      throw new Error(`Invalid payload for event ${String(event)}`);
    }
    
    // Transform payload
    let transformedPayload = payload;
    if (config?.transformer) {
      transformedPayload = config.transformer(payload, effectiveContext);
    }
    
    // Record in history
    this.recordEvent(event, transformedPayload, effectiveContext);
    
    // Build interceptor chain
    const interceptorChain = this.buildInterceptorChain(
      event,
      transformedPayload,
      effectiveContext
    );
    
    // Execute with interceptors
    await interceptorChain();
  }
  
  intercept<K extends keyof TEventMap>(
    event: K,
    interceptor: (
      payload: TEventMap[K],
      context: TContext,
      next: () => Promise<void>
    ) => Promise<void>
  ): () => void {
    if (!this.interceptors.has(event)) {
      this.interceptors.set(event, []);
    }
    
    this.interceptors.get(event)!.push(interceptor);
    
    return () => {
      const interceptors = this.interceptors.get(event);
      if (interceptors) {
        const index = interceptors.indexOf(interceptor);
        if (index > -1) {
          interceptors.splice(index, 1);
        }
      }
    };
  }
  
  async replay<K extends keyof TEventMap>(
    event: K,
    filter?: (payload: TEventMap[K], timestamp: Date) => boolean
  ): Promise<void> {
    const records = this.history.get(event) || [];
    
    for (const record of records) {
      if (!filter || filter(record.payload, record.timestamp)) {
        await this.executeHandlers(event, record.payload, record.context);
      }
    }
  }
  
  getHistory<K extends keyof TEventMap>(
    event?: K
  ): EventRecord<K extends keyof TEventMap ? TEventMap[K] : any>[] {
    if (event) {
      return this.history.get(event) || [];
    }
    
    const allHistory: EventRecord<any>[] = [];
    this.history.forEach((records) => {
      allHistory.push(...records);
    });
    
    return allHistory.sort((a, b) => 
      a.timestamp.getTime() - b.timestamp.getTime()
    );
  }
  
  clear(event?: keyof TEventMap): void {
    if (event) {
      this.handlers.delete(event);
      this.onceHandlers.delete(event);
      this.interceptors.delete(event);
      this.history.delete(event);
      this.configs.delete(event);
    } else {
      this.handlers.clear();
      this.onceHandlers.clear();
      this.interceptors.clear();
      this.history.clear();
      this.configs.clear();
    }
  }
  
  configure<K extends keyof TEventMap>(
    config: EventConfig<K, TEventMap[K], TContext>
  ): void {
    this.configs.set(config.name, config);
  }
  
  private recordEvent<K extends keyof TEventMap>(
    event: K,
    payload: TEventMap[K],
    context: TContext
  ): void {
    if (!this.history.has(event)) {
      this.history.set(event, []);
    }
    
    const config = this.configs.get(event);
    
    this.history.get(event)!.push({
      payload,
      timestamp: new Date(),
      context,
      metadata: config?.metadata
    });
    
    // Limit history size
    const maxHistorySize = 1000;
    const history = this.history.get(event)!;
    if (history.length > maxHistorySize) {
      history.splice(0, history.length - maxHistorySize);
    }
  }
  
  private buildInterceptorChain<K extends keyof TEventMap>(
    event: K,
    payload: TEventMap[K],
    context: TContext
  ): () => Promise<void> {
    const interceptors = this.interceptors.get(event) || [];
    
    const executeCore = async () => {
      await this.executeHandlers(event, payload, context);
    };
    
    if (interceptors.length === 0) {
      return executeCore;
    }
    
    // Build chain from interceptors
    return interceptors.reduceRight(
      (next, interceptor) => {
        return async () => {
          await interceptor(payload, context, next);
        };
      },
      executeCore
    );
  }
  
  private async executeHandlers<K extends keyof TEventMap>(
    event: K,
    payload: TEventMap[K],
    context: TContext
  ): Promise<void> {
    // Execute regular handlers
    const handlers = this.handlers.get(event);
    if (handlers) {
      const promises: Promise<void>[] = [];
      
      handlers.forEach(handler => {
        const result = handler(payload, context);
        if (result instanceof Promise) {
          promises.push(result);
        }
      });
      
      await Promise.all(promises);
    }
    
    // Execute once handlers
    const onceHandlers = this.onceHandlers.get(event);
    if (onceHandlers) {
      const handlersToExecute = Array.from(onceHandlers);
      onceHandlers.clear();
      
      const promises: Promise<void>[] = [];
      
      handlersToExecute.forEach(handler => {
        const result = handler(payload, context);
        if (result instanceof Promise) {
          promises.push(result);
        }
      });
      
      await Promise.all(promises);
    }
  }
}

// 🎨 Factory with middleware support
interface EventBusMiddleware<TEventMap, TContext> {
  beforeEmit?<K extends keyof TEventMap>(
    event: K,
    payload: TEventMap[K],
    context: TContext
  ): void | Promise<void>;
  
  afterEmit?<K extends keyof TEventMap>(
    event: K,
    payload: TEventMap[K],
    context: TContext
  ): void | Promise<void>;
  
  onError?<K extends keyof TEventMap>(
    event: K,
    error: Error,
    payload: TEventMap[K],
    context: TContext
  ): void | Promise<void>;
}

function createEventBus<
  TEventMap extends Record<string, any>,
  TContext = {}
>(
  defaultContext: TContext,
  middleware?: EventBusMiddleware<TEventMap, TContext>
): EventBus<TEventMap, TContext> {
  const bus = new AdvancedEventBus<TEventMap, TContext>(defaultContext);
  
  if (middleware) {
    // Wrap emit to include middleware
    const originalEmit = bus.emit.bind(bus);
    
    bus.emit = async function<K extends keyof TEventMap>(
      event: K,
      payload: TEventMap[K],
      context?: TContext
    ): Promise<void> {
      const effectiveContext = context || defaultContext;
      
      try {
        await middleware.beforeEmit?.(event, payload, effectiveContext);
        await originalEmit(event, payload, context);
        await middleware.afterEmit?.(event, payload, effectiveContext);
      } catch (error) {
        await middleware.onError?.(
          event,
          error as Error,
          payload,
          effectiveContext
        );
        throw error;
      }
    };
  }
  
  return bus;
}

// 💫 Test the implementation
interface AppEvents {
  userLogin: { userId: string; timestamp: Date; ip: string };
  userLogout: { userId: string; reason?: string };
  dataUpdate: { table: string; id: string; changes: any; user: string };
  error: { code: string; message: string; stack?: string; severity: 'low' | 'medium' | 'high' };
  performance: { metric: string; value: number; unit: string };
}

interface AppContext {
  requestId: string;
  userId?: string;
  permissions: string[];
  timestamp: Date;
}

async function testEventSystem() {
  console.log('=== Advanced Event System Test ===\n');
  
  const eventBus = createEventBus<AppEvents, AppContext>(
    {
      requestId: 'default',
      permissions: [],
      timestamp: new Date()
    },
    {
      beforeEmit: async (event, payload, context) => {
        console.log(`[Middleware] Before ${String(event)}:`, payload);
      },
      afterEmit: async (event, payload, context) => {
        console.log(`[Middleware] After ${String(event)}`);
      },
      onError: async (event, error, payload, context) => {
        console.error(`[Middleware] Error in ${String(event)}:`, error.message);
      }
    }
  );
  
  // Configure events
  eventBus.configure({
    name: 'userLogin',
    validator: (payload) => payload.userId.length > 0,
    transformer: (payload, context) => ({
      ...payload,
      timestamp: new Date()
    }),
    metadata: { critical: true }
  });
  
  eventBus.configure({
    name: 'error',
    validator: (payload) => payload.code.length > 0,
    metadata: { logLevel: 'error' }
  });
  
  // Set up handlers
  eventBus.on('userLogin', async (payload, context) => {
    console.log(`🔐 User ${payload.userId} logged in from ${payload.ip}`);
    console.log(`   Request: ${context.requestId}`);
  });
  
  eventBus.once('userLogin', async (payload) => {
    console.log(`🎉 First login detected for ${payload.userId}`);
  });
  
  const unsubscribe = eventBus.on('dataUpdate', async (payload, context) => {
    console.log(`📝 Data update in ${payload.table} by ${payload.user}`);
  });
  
  eventBus.on('error', async (payload) => {
    const emoji = payload.severity === 'high' ? '🚨' : 
                 payload.severity === 'medium' ? '⚠️' : 'ℹ️';
    console.log(`${emoji} Error ${payload.code}: ${payload.message}`);
  });
  
  // Set up interceptors
  eventBus.intercept('dataUpdate', async (payload, context, next) => {
    console.log('🔍 Validating permissions for data update...');
    
    if (!context.permissions.includes('write')) {
      throw new Error('Insufficient permissions');
    }
    
    console.log('✅ Permissions validated');
    await next();
    console.log('📊 Logging data update to audit trail');
  });
  
  eventBus.intercept('error', async (payload, context, next) => {
    if (payload.severity === 'high') {
      console.log('🚨 Sending alert for high severity error');
    }
    await next();
  });
  
  // Emit events
  const context: AppContext = {
    requestId: 'req-123',
    userId: 'user-456',
    permissions: ['read', 'write'],
    timestamp: new Date()
  };
  
  console.log('\n--- Emitting Events ---\n');
  
  await eventBus.emit('userLogin', {
    userId: 'alice',
    timestamp: new Date(),
    ip: '192.168.1.1'
  }, context);
  
  await eventBus.emit('dataUpdate', {
    table: 'users',
    id: '123',
    changes: { name: 'Alice Smith' },
    user: 'alice'
  }, context);
  
  await eventBus.emit('error', {
    code: 'DB_001',
    message: 'Database connection failed',
    severity: 'high'
  }, context);
  
  await eventBus.emit('performance', {
    metric: 'response_time',
    value: 125,
    unit: 'ms'
  }, context);
  
  // Test history and replay
  console.log('\n--- Event History ---\n');
  const loginHistory = eventBus.getHistory('userLogin');
  console.log(`Login events: ${loginHistory.length}`);
  
  console.log('\n--- Replaying Events ---\n');
  await eventBus.replay('userLogin');
  
  // Unsubscribe handler
  unsubscribe();
  
  // Clear specific event
  eventBus.clear('performance');
  
  console.log('\n✅ Test complete!');
}

testEventSystem();

🎯 Summary

You’ve mastered multiple type parameters in TypeScript! 🎉 You learned how to:

  • 🧩 Create complex multi-parameter generic types
  • 🔗 Build sophisticated type relationships and constraints
  • 🎭 Work with higher-order type parameters
  • 🔄 Implement advanced patterns like state machines
  • 🗺️ Design complex data transformation systems
  • ✨ Create professional-grade generic libraries

Multiple type parameters unlock the full power of TypeScript’s type system, enabling you to create incredibly flexible yet type-safe code. You’re now equipped to tackle the most complex generic programming challenges!

Keep building amazing generic systems! 🚀