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

mixin-detection.mddocs/

0

# Mixin Detection

1

2

Type-safe mixin detection and type narrowing functionality. Since `instanceof` doesn't work with mixins created by ts-mixer, the `hasMixin` function provides an equivalent capability with full type narrowing support.

3

4

## Capabilities

5

6

### hasMixin Function

7

8

Determines whether an instance has a specific mixin, providing type narrowing similar to `instanceof`.

9

10

```typescript { .api }

11

/**

12

* Determines whether an instance has a specific mixin

13

* Provides type narrowing similar to instanceof

14

* @param instance - Object to test for mixin presence

15

* @param mixin - Class constructor to check for

16

* @returns Type guard indicating if instance has the mixin

17

*/

18

function hasMixin<M>(

19

instance: any,

20

mixin: abstract new (...args) => M

21

): instance is M;

22

```

23

24

**Basic Usage:**

25

26

```typescript

27

import { Mixin, hasMixin } from "ts-mixer";

28

29

class Foo {

30

fooProperty: string = "foo";

31

fooMethod(): string {

32

return "from foo";

33

}

34

}

35

36

class Bar {

37

barProperty: string = "bar";

38

barMethod(): string {

39

return "from bar";

40

}

41

}

42

43

class FooBar extends Mixin(Foo, Bar) {

44

combinedMethod(): string {

45

return this.fooMethod() + " and " + this.barMethod();

46

}

47

}

48

49

const instance = new FooBar();

50

51

// instanceof doesn't work with mixins

52

console.log(instance instanceof FooBar); // true

53

console.log(instance instanceof Foo); // false ❌

54

console.log(instance instanceof Bar); // false ❌

55

56

// hasMixin works correctly

57

console.log(hasMixin(instance, FooBar)); // true

58

console.log(hasMixin(instance, Foo)); // true ✅

59

console.log(hasMixin(instance, Bar)); // true ✅

60

```

61

62

## Type Narrowing

63

64

The `hasMixin` function provides full TypeScript type narrowing:

65

66

```typescript

67

import { Mixin, hasMixin } from "ts-mixer";

68

69

class Drawable {

70

draw(): void {

71

console.log("Drawing...");

72

}

73

}

74

75

class Movable {

76

x: number = 0;

77

y: number = 0;

78

79

move(dx: number, dy: number): void {

80

this.x += dx;

81

this.y += dy;

82

}

83

}

84

85

class Shape extends Mixin(Drawable, Movable) {

86

area: number = 0;

87

}

88

89

function processObject(obj: any) {

90

// Type narrowing with hasMixin

91

if (hasMixin(obj, Drawable)) {

92

obj.draw(); // ✅ TypeScript knows obj has draw() method

93

}

94

95

if (hasMixin(obj, Movable)) {

96

obj.move(10, 20); // ✅ TypeScript knows obj has move() method

97

console.log(obj.x, obj.y); // ✅ TypeScript knows obj has x, y properties

98

}

99

100

if (hasMixin(obj, Shape)) {

101

obj.draw(); // ✅ Available from Drawable

102

obj.move(5, 5); // ✅ Available from Movable

103

console.log(obj.area); // ✅ Available from Shape

104

}

105

}

106

107

const shape = new Shape();

108

processObject(shape); // All type checks pass

109

```

110

111

## Advanced Usage Examples

112

113

### Deep Mixin Hierarchies

114

115

`hasMixin` works with complex mixin hierarchies and nested mixins:

116

117

```typescript

118

import { Mixin, hasMixin } from "ts-mixer";

119

120

class A {

121

methodA(): string { return "A"; }

122

}

123

124

class B {

125

methodB(): string { return "B"; }

126

}

127

128

class AB extends Mixin(A, B) {

129

methodAB(): string { return "AB"; }

130

}

131

132

class C {

133

methodC(): string { return "C"; }

134

}

135

136

class D {

137

methodD(): string { return "D"; }

138

}

139

140

class CD extends Mixin(C, D) {

141

methodCD(): string { return "CD"; }

142

}

143

144

// Mixing already-mixed classes

145

class SuperMixed extends Mixin(AB, CD) {

146

superMethod(): string {

147

return "Super";

148

}

149

}

150

151

class ExtraLayer extends SuperMixed {}

152

153

const instance = new ExtraLayer();

154

155

// hasMixin traverses the entire hierarchy

156

console.log(hasMixin(instance, A)); // true

157

console.log(hasMixin(instance, B)); // true

158

console.log(hasMixin(instance, AB)); // true

159

console.log(hasMixin(instance, C)); // true

160

console.log(hasMixin(instance, D)); // true

161

console.log(hasMixin(instance, CD)); // true

162

console.log(hasMixin(instance, SuperMixed)); // true

163

console.log(hasMixin(instance, ExtraLayer)); // true

164

165

// Type narrowing works at any level

166

if (hasMixin(instance, A)) {

167

instance.methodA(); // ✅ Available

168

}

169

170

if (hasMixin(instance, CD)) {

171

instance.methodC(); // ✅ Available from C

172

instance.methodD(); // ✅ Available from D

173

instance.methodCD(); // ✅ Available from CD

174

}

175

```

176

177

### Abstract Class Support

178

179

`hasMixin` works with abstract classes:

180

181

```typescript

182

import { Mixin, hasMixin } from "ts-mixer";

183

184

abstract class Animal {

185

abstract makeSound(): string;

186

187

name: string;

188

constructor(name: string) {

189

this.name = name;

190

}

191

}

192

193

abstract class Flyable {

194

abstract fly(): string;

195

196

altitude: number = 0;

197

198

ascend(height: number): void {

199

this.altitude += height;

200

}

201

}

202

203

class Bird extends Mixin(Animal, Flyable) {

204

constructor(name: string) {

205

super(name);

206

}

207

208

makeSound(): string {

209

return "Tweet!";

210

}

211

212

fly(): string {

213

return `${this.name} is flying at ${this.altitude} feet`;

214

}

215

}

216

217

const bird = new Bird("Robin");

218

219

console.log(hasMixin(bird, Animal)); // true

220

console.log(hasMixin(bird, Flyable)); // true

221

console.log(hasMixin(bird, Bird)); // true

222

223

// Type narrowing with abstract classes

224

if (hasMixin(bird, Animal)) {

225

console.log(bird.name); // ✅ Available

226

console.log(bird.makeSound()); // ✅ Available

227

}

228

229

if (hasMixin(bird, Flyable)) {

230

bird.ascend(100); // ✅ Available

231

console.log(bird.fly()); // ✅ Available

232

}

233

```

234

235

### Conditional Logic with Multiple Mixins

236

237

```typescript

238

import { Mixin, hasMixin } from "ts-mixer";

239

240

interface Serializable {

241

serialize(): string;

242

}

243

244

class JsonSerializable implements Serializable {

245

serialize(): string {

246

return JSON.stringify(this);

247

}

248

}

249

250

interface Cacheable {

251

cacheKey: string;

252

}

253

254

class SimpleCacheable implements Cacheable {

255

cacheKey: string;

256

257

constructor(key: string) {

258

this.cacheKey = key;

259

}

260

}

261

262

interface Loggable {

263

log(message: string): void;

264

}

265

266

class ConsoleLoggable implements Loggable {

267

log(message: string): void {

268

console.log(`[${new Date().toISOString()}]: ${message}`);

269

}

270

}

271

272

// Various combinations of mixins

273

class DataObject extends Mixin(JsonSerializable, SimpleCacheable) {

274

data: any;

275

276

constructor(data: any, cacheKey: string) {

277

super(cacheKey);

278

this.data = data;

279

}

280

}

281

282

class LoggableDataObject extends Mixin(DataObject, ConsoleLoggable) {}

283

284

function processObject(obj: any) {

285

let capabilities: string[] = [];

286

287

if (hasMixin(obj, JsonSerializable)) {

288

capabilities.push("serializable");

289

const serialized = obj.serialize(); // ✅ Type-safe access

290

}

291

292

if (hasMixin(obj, SimpleCacheable)) {

293

capabilities.push("cacheable");

294

const key = obj.cacheKey; // ✅ Type-safe access

295

}

296

297

if (hasMixin(obj, ConsoleLoggable)) {

298

capabilities.push("loggable");

299

obj.log("Processing object"); // ✅ Type-safe access

300

}

301

302

console.log(`Object has capabilities: ${capabilities.join(", ")}`);

303

}

304

305

const dataObj = new DataObject({ value: 42 }, "my-key");

306

const loggableDataObj = new LoggableDataObject({ value: 100 }, "logged-key");

307

308

processObject(dataObj); // "serializable, cacheable"

309

processObject(loggableDataObj); // "serializable, cacheable, loggable"

310

```

311

312

## Implementation Details

313

314

### Search Algorithm

315

316

`hasMixin` uses a breadth-first search algorithm to traverse:

317

318

1. **Direct instanceof check**: First checks regular instanceof

319

2. **Mixin constituents**: Examines registered mixin components

320

3. **Prototype chain**: Traverses the prototype chain for inheritance

321

4. **Recursive search**: Continues until all paths are exhausted

322

323

### Performance Considerations

324

325

- Uses `WeakMap` for mixin tracking to avoid memory leaks

326

- Breadth-first search prevents infinite loops in circular references

327

- Caches visited classes during search to avoid redundant checks

328

329

### Limitations

330

331

- Only works with classes mixed using ts-mixer

332

- Requires ES6 `Map` support (or polyfill)

333

- Performance is O(n) where n is the size of the mixin hierarchy