CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ts-mixer

A TypeScript library that provides tolerable Mixin functionality with multiple inheritance support.

Pending
Overview
Eval results
Files

generic-classes.mddocs/

Generic Classes

Specialized mixing support for generic classes using the decorator pattern. Due to TypeScript limitations with generic parameters in base class expressions, generic class mixing requires a different approach using the mix decorator combined with declaration merging.

Capabilities

Mix Decorator

A decorator version of the Mixin function specifically designed for mixing generic classes.

/**
 * A decorator version of the Mixin function for mixing generic classes
 * Used when generic parameters prevent using Mixin in extends clause
 * @param ingredients - Array of class constructors to mix
 * @returns Class decorator function
 */
function mix(...ingredients: Class[]): (decoratedClass: any) => any;

Usage Pattern:

The mix decorator must be used in combination with declaration merging to provide proper typing:

import { mix } from "ts-mixer";

// Base generic classes to mix
class Foo<T> {
  public fooMethod(input: T): T {
    return input;
  }
}

class Bar<T> {
  public barMethod(input: T): T {
    return input;
  }
}

// Declaration merging: interface provides the typing
interface FooBar<T1, T2> extends Foo<T1>, Bar<T2> {}

// Decorator provides the runtime behavior
@mix(Foo, Bar)
class FooBar<T1, T2> {
  public fooBarMethod(input1: T1, input2: T2) {
    return [this.fooMethod(input1), this.barMethod(input2)];
  }
}

// Usage
const instance = new FooBar<string, number>();
const result = instance.fooBarMethod("hello", 42);
// result: ["hello", 42]
// TypeScript knows fooMethod and barMethod are available

Key Differences from Regular Mixin

Declaration Merging Requirement

Unlike the regular Mixin function, mix requires explicit declaration merging:

// This interface declaration is REQUIRED
interface MixedClass<T1, T2> extends BaseClass1<T1>, BaseClass2<T2> {}

// The decorator provides runtime behavior but no typing
@mix(BaseClass1, BaseClass2)
class MixedClass<T1, T2> {
  // Your additional methods here
}

No Type Inference

The mix decorator does not provide automatic type inference like Mixin. You must manually specify the interface that extends all mixed classes.

Runtime vs Compile-time

  • Interface declaration: Provides TypeScript typing at compile-time
  • @mix decorator: Provides actual mixin behavior at runtime
  • Both are required for generic class mixing to work properly

Advanced Usage Examples

Multiple Generic Parameters

import { mix } from "ts-mixer";

class Storage<T> {
  private items: T[] = [];
  
  store(item: T): void {
    this.items.push(item);
  }
  
  retrieve(): T[] {
    return [...this.items];
  }
}

class Validator<T> {
  private rules: ((item: T) => boolean)[] = [];
  
  addRule(rule: (item: T) => boolean): void {
    this.rules.push(rule);
  }
  
  validate(item: T): boolean {
    return this.rules.every(rule => rule(item));
  }
}

class Logger<T> {
  log(message: string, item?: T): void {
    console.log(`[LOG]: ${message}`, item);
  }
}

// Declaration merging for all three generic classes
interface ValidatedStorage<T> extends Storage<T>, Validator<T>, Logger<T> {}

@mix(Storage, Validator, Logger)
class ValidatedStorage<T> {
  storeWithValidation(item: T): boolean {
    this.log("Attempting to store item", item);
    
    if (this.validate(item)) {
      this.store(item);
      this.log("Item stored successfully");
      return true;
    } else {
      this.log("Item validation failed");
      return false;
    }
  }
}

// Usage
const storage = new ValidatedStorage<number>();
storage.addRule(x => x > 0);
storage.addRule(x => x < 100);

storage.storeWithValidation(50); // true, logs success
storage.storeWithValidation(-5); // false, logs validation failure

const items = storage.retrieve(); // [50]

Generic Class Hierarchies

import { mix } from "ts-mixer";

// Base generic class
class Container<T> {
  protected items: T[] = [];
  
  add(item: T): void {
    this.items.push(item);
  }
}

// Another generic class extending Container
class SortedContainer<T> extends Container<T> {
  add(item: T): void {
    super.add(item);
    this.sort();
  }
  
  private sort(): void {
    this.items.sort();
  }
}

// Mixin class with different generic parameter
class Timestamped<U> {
  private timestamps: Map<U, Date> = new Map();
  
  setTimestamp(key: U, date: Date = new Date()): void {
    this.timestamps.set(key, date);
  }
  
  getTimestamp(key: U): Date | undefined {
    return this.timestamps.get(key);
  }
}

// Mixing a class hierarchy with another generic class
interface TimestampedSortedContainer<T, U> 
  extends SortedContainer<T>, Timestamped<U> {}

@mix(SortedContainer, Timestamped)
class TimestampedSortedContainer<T, U> {
  addWithTimestamp(item: T, key: U): void {
    this.add(item);
    this.setTimestamp(key, new Date());
  }
}

// Usage
const container = new TimestampedSortedContainer<string, number>();
container.addWithTimestamp("hello", 1);
container.addWithTimestamp("world", 2);

const timestamp = container.getTimestamp(1);
console.log(timestamp); // Date object

Limitations and Considerations

TypeScript Limitations

Generic class mixing exists because TypeScript doesn't allow generic parameters in base class expressions:

// This is IMPOSSIBLE in TypeScript
class Mixed<T> extends Mixin(Generic1<T>, Generic2<T>) {} // ❌ Error

Declaration Merging Requirement

You must always provide the interface declaration. Without it, TypeScript won't know about the mixed-in methods and properties:

// Missing interface - TypeScript won't know about mixed methods
@mix(ClassA, ClassB)  // ❌ Properties from ClassA, ClassB not available
class BadExample<T> {}

// Correct approach
interface GoodExample<T> extends ClassA<T>, ClassB<T> {}
@mix(ClassA, ClassB)  // ✅ Properties available via interface
class GoodExample<T> {}

Order Dependency

The order of classes in the mix decorator affects method override precedence, just like with regular Mixin.

Constructor Limitations

The same constructor limitations apply as with regular mixing - constructors receive separate this contexts.

Install with Tessl CLI

npx tessl i tessl/npm-ts-mixer

docs

configuration.md

core-mixing.md

decorators.md

generic-classes.md

index.md

mixin-detection.md

tile.json