or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-mixing.mddecorators.mdgeneric-classes.mdindex.mdmixin-detection.md

generic-classes.mddocs/

0

# Generic Classes

1

2

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.

3

4

## Capabilities

5

6

### Mix Decorator

7

8

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

9

10

```typescript { .api }

11

/**

12

* A decorator version of the Mixin function for mixing generic classes

13

* Used when generic parameters prevent using Mixin in extends clause

14

* @param ingredients - Array of class constructors to mix

15

* @returns Class decorator function

16

*/

17

function mix(...ingredients: Class[]): (decoratedClass: any) => any;

18

```

19

20

**Usage Pattern:**

21

22

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

23

24

```typescript

25

import { mix } from "ts-mixer";

26

27

// Base generic classes to mix

28

class Foo<T> {

29

public fooMethod(input: T): T {

30

return input;

31

}

32

}

33

34

class Bar<T> {

35

public barMethod(input: T): T {

36

return input;

37

}

38

}

39

40

// Declaration merging: interface provides the typing

41

interface FooBar<T1, T2> extends Foo<T1>, Bar<T2> {}

42

43

// Decorator provides the runtime behavior

44

@mix(Foo, Bar)

45

class FooBar<T1, T2> {

46

public fooBarMethod(input1: T1, input2: T2) {

47

return [this.fooMethod(input1), this.barMethod(input2)];

48

}

49

}

50

51

// Usage

52

const instance = new FooBar<string, number>();

53

const result = instance.fooBarMethod("hello", 42);

54

// result: ["hello", 42]

55

// TypeScript knows fooMethod and barMethod are available

56

```

57

58

## Key Differences from Regular Mixin

59

60

### Declaration Merging Requirement

61

62

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

63

64

```typescript

65

// This interface declaration is REQUIRED

66

interface MixedClass<T1, T2> extends BaseClass1<T1>, BaseClass2<T2> {}

67

68

// The decorator provides runtime behavior but no typing

69

@mix(BaseClass1, BaseClass2)

70

class MixedClass<T1, T2> {

71

// Your additional methods here

72

}

73

```

74

75

### No Type Inference

76

77

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

78

79

### Runtime vs Compile-time

80

81

- **Interface declaration**: Provides TypeScript typing at compile-time

82

- **@mix decorator**: Provides actual mixin behavior at runtime

83

- Both are required for generic class mixing to work properly

84

85

## Advanced Usage Examples

86

87

### Multiple Generic Parameters

88

89

```typescript

90

import { mix } from "ts-mixer";

91

92

class Storage<T> {

93

private items: T[] = [];

94

95

store(item: T): void {

96

this.items.push(item);

97

}

98

99

retrieve(): T[] {

100

return [...this.items];

101

}

102

}

103

104

class Validator<T> {

105

private rules: ((item: T) => boolean)[] = [];

106

107

addRule(rule: (item: T) => boolean): void {

108

this.rules.push(rule);

109

}

110

111

validate(item: T): boolean {

112

return this.rules.every(rule => rule(item));

113

}

114

}

115

116

class Logger<T> {

117

log(message: string, item?: T): void {

118

console.log(`[LOG]: ${message}`, item);

119

}

120

}

121

122

// Declaration merging for all three generic classes

123

interface ValidatedStorage<T> extends Storage<T>, Validator<T>, Logger<T> {}

124

125

@mix(Storage, Validator, Logger)

126

class ValidatedStorage<T> {

127

storeWithValidation(item: T): boolean {

128

this.log("Attempting to store item", item);

129

130

if (this.validate(item)) {

131

this.store(item);

132

this.log("Item stored successfully");

133

return true;

134

} else {

135

this.log("Item validation failed");

136

return false;

137

}

138

}

139

}

140

141

// Usage

142

const storage = new ValidatedStorage<number>();

143

storage.addRule(x => x > 0);

144

storage.addRule(x => x < 100);

145

146

storage.storeWithValidation(50); // true, logs success

147

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

148

149

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

150

```

151

152

### Generic Class Hierarchies

153

154

```typescript

155

import { mix } from "ts-mixer";

156

157

// Base generic class

158

class Container<T> {

159

protected items: T[] = [];

160

161

add(item: T): void {

162

this.items.push(item);

163

}

164

}

165

166

// Another generic class extending Container

167

class SortedContainer<T> extends Container<T> {

168

add(item: T): void {

169

super.add(item);

170

this.sort();

171

}

172

173

private sort(): void {

174

this.items.sort();

175

}

176

}

177

178

// Mixin class with different generic parameter

179

class Timestamped<U> {

180

private timestamps: Map<U, Date> = new Map();

181

182

setTimestamp(key: U, date: Date = new Date()): void {

183

this.timestamps.set(key, date);

184

}

185

186

getTimestamp(key: U): Date | undefined {

187

return this.timestamps.get(key);

188

}

189

}

190

191

// Mixing a class hierarchy with another generic class

192

interface TimestampedSortedContainer<T, U>

193

extends SortedContainer<T>, Timestamped<U> {}

194

195

@mix(SortedContainer, Timestamped)

196

class TimestampedSortedContainer<T, U> {

197

addWithTimestamp(item: T, key: U): void {

198

this.add(item);

199

this.setTimestamp(key, new Date());

200

}

201

}

202

203

// Usage

204

const container = new TimestampedSortedContainer<string, number>();

205

container.addWithTimestamp("hello", 1);

206

container.addWithTimestamp("world", 2);

207

208

const timestamp = container.getTimestamp(1);

209

console.log(timestamp); // Date object

210

```

211

212

## Limitations and Considerations

213

214

### TypeScript Limitations

215

216

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

217

218

```typescript

219

// This is IMPOSSIBLE in TypeScript

220

class Mixed<T> extends Mixin(Generic1<T>, Generic2<T>) {} // ❌ Error

221

```

222

223

### Declaration Merging Requirement

224

225

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

226

227

```typescript

228

// Missing interface - TypeScript won't know about mixed methods

229

@mix(ClassA, ClassB) // ❌ Properties from ClassA, ClassB not available

230

class BadExample<T> {}

231

232

// Correct approach

233

interface GoodExample<T> extends ClassA<T>, ClassB<T> {}

234

@mix(ClassA, ClassB) // ✅ Properties available via interface

235

class GoodExample<T> {}

236

```

237

238

### Order Dependency

239

240

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

241

242

### Constructor Limitations

243

244

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