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

decorators.mddocs/

0

# Decorators

1

2

Decorator preservation and inheritance system for mixed classes. The `decorate` function ensures that decorators are properly inherited when classes are mixed together, solving the problem where decorators from mixin classes are lost.

3

4

## Capabilities

5

6

### Decorate Function

7

8

Wraps decorators to ensure they are inherited during the mixing process.

9

10

```typescript { .api }

11

/**

12

* Wraps decorators to ensure they participate in mixin inheritance

13

* Supports class, property, and method decorators

14

* @param decorator - Any decorator (class, property, or method)

15

* @returns Wrapped decorator that will be inherited during mixing

16

*/

17

function decorate<T extends ClassDecorator | PropertyDecorator | MethodDecorator>(

18

decorator: T

19

): T;

20

```

21

22

**Usage Pattern:**

23

24

Replace regular decorators with `decorate(decorator)` in classes that will be mixed:

25

26

```typescript

27

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

28

29

// Instead of @SomeDecorator()

30

// Use @decorate(SomeDecorator())

31

32

class Validatable {

33

@decorate(IsString()) // instead of @IsString()

34

name: string = "";

35

36

@decorate(IsNumber()) // instead of @IsNumber()

37

age: number = 0;

38

}

39

```

40

41

## Decorator Types

42

43

### Class Decorators

44

45

Class decorators are inherited and applied to the mixed class:

46

47

```typescript

48

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

49

50

// Custom class decorator

51

function Entity(tableName: string) {

52

return function<T extends { new(...args: any[]): {} }>(constructor: T) {

53

(constructor as any).tableName = tableName;

54

return constructor;

55

};

56

}

57

58

@decorate(Entity("users"))

59

class User {

60

id: number = 0;

61

name: string = "";

62

}

63

64

@decorate(Entity("profiles"))

65

class Profile {

66

bio: string = "";

67

avatar: string = "";

68

}

69

70

class UserProfile extends Mixin(User, Profile) {

71

fullProfile(): string {

72

return `${this.name}: ${this.bio}`;

73

}

74

}

75

76

// Both decorators are applied to the mixed class

77

console.log((UserProfile as any).tableName); // "profiles" (last one wins)

78

```

79

80

### Property Decorators

81

82

Property decorators are inherited for both instance and static properties:

83

84

```typescript

85

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

86

import { IsEmail, IsString, Length } from "class-validator";

87

88

class PersonalInfo {

89

@decorate(IsString())

90

@decorate(Length(2, 50))

91

firstName: string = "";

92

93

@decorate(IsString())

94

@decorate(Length(2, 50))

95

lastName: string = "";

96

}

97

98

class ContactInfo {

99

@decorate(IsEmail())

100

email: string = "";

101

102

@decorate(IsString())

103

phone: string = "";

104

}

105

106

class Person extends Mixin(PersonalInfo, ContactInfo) {

107

getFullName(): string {

108

return `${this.firstName} ${this.lastName}`;

109

}

110

}

111

112

// All property decorators are inherited

113

const person = new Person();

114

person.firstName = "John";

115

person.lastName = "Doe";

116

person.email = "john.doe@example.com";

117

118

// Validation decorators work on the mixed class

119

import { validate } from "class-validator";

120

121

validate(person).then(errors => {

122

console.log("Validation errors:", errors);

123

});

124

```

125

126

### Method Decorators

127

128

Method decorators are inherited and applied to methods in the mixed class:

129

130

```typescript

131

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

132

133

// Custom method decorator for logging

134

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

135

const originalMethod = descriptor.value;

136

137

descriptor.value = function(...args: any[]) {

138

console.log(`Calling ${propertyKey} with args:`, args);

139

const result = originalMethod.apply(this, args);

140

console.log(`${propertyKey} returned:`, result);

141

return result;

142

};

143

144

return descriptor;

145

}

146

147

class Calculator {

148

@decorate(Log)

149

add(a: number, b: number): number {

150

return a + b;

151

}

152

}

153

154

class StringUtils {

155

@decorate(Log)

156

reverse(str: string): string {

157

return str.split("").reverse().join("");

158

}

159

}

160

161

class MathStringUtils extends Mixin(Calculator, StringUtils) {

162

@decorate(Log)

163

addAndReverse(a: number, b: number): string {

164

const sum = this.add(a, b);

165

return this.reverse(sum.toString());

166

}

167

}

168

169

const utils = new MathStringUtils();

170

const result = utils.addAndReverse(12, 34); // Logs all method calls

171

// Calling add with args: [12, 34]

172

// add returned: 46

173

// Calling reverse with args: ["46"]

174

// reverse returned: "64"

175

// Calling addAndReverse with args: [12, 34]

176

// addAndReverse returned: "64"

177

```

178

179

## Advanced Usage Examples

180

181

### Class Validator Integration

182

183

Real-world example with `class-validator` decorators:

184

185

```typescript

186

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

187

import {

188

IsString, IsNumber, IsEmail, IsBoolean, IsOptional,

189

Length, Min, Max, validate

190

} from "class-validator";

191

192

class UserValidation {

193

@decorate(IsString())

194

@decorate(Length(3, 20))

195

username: string = "";

196

197

@decorate(IsEmail())

198

email: string = "";

199

200

@decorate(IsNumber())

201

@decorate(Min(18))

202

@decorate(Max(120))

203

age: number = 0;

204

}

205

206

class ProfileValidation {

207

@decorate(IsString())

208

@decorate(IsOptional())

209

@decorate(Length(0, 500))

210

bio?: string;

211

212

@decorate(IsBoolean())

213

isPublic: boolean = false;

214

215

@decorate(IsString())

216

@decorate(IsOptional())

217

website?: string;

218

}

219

220

class ValidatedUser extends Mixin(UserValidation, ProfileValidation) {

221

constructor(data: Partial<ValidatedUser>) {

222

super();

223

Object.assign(this, data);

224

}

225

226

async isValid(): Promise<boolean> {

227

const errors = await validate(this);

228

return errors.length === 0;

229

}

230

}

231

232

// Usage with validation

233

const user = new ValidatedUser({

234

username: "johndoe",

235

email: "john@example.com",

236

age: 25,

237

bio: "Software developer",

238

isPublic: true

239

});

240

241

user.isValid().then(valid => {

242

console.log("User is valid:", valid); // true

243

});

244

245

const invalidUser = new ValidatedUser({

246

username: "jo", // too short

247

email: "invalid-email",

248

age: 15 // too young

249

});

250

251

invalidUser.isValid().then(valid => {

252

console.log("User is valid:", valid); // false

253

});

254

```

255

256

### Multiple Decorator Types

257

258

Combining different types of decorators in mixed classes:

259

260

```typescript

261

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

262

263

// Class decorator

264

function Serializable<T extends { new(...args: any[]): {} }>(constructor: T) {

265

return class extends constructor {

266

serialize() {

267

return JSON.stringify(this);

268

}

269

};

270

}

271

272

// Property decorator

273

function Computed(target: any, propertyKey: string) {

274

console.log(`Property ${propertyKey} is computed`);

275

}

276

277

// Method decorator

278

function Cached(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

279

const cache = new Map();

280

const originalMethod = descriptor.value;

281

282

descriptor.value = function(...args: any[]) {

283

const key = JSON.stringify(args);

284

if (cache.has(key)) {

285

return cache.get(key);

286

}

287

288

const result = originalMethod.apply(this, args);

289

cache.set(key, result);

290

return result;

291

};

292

293

return descriptor;

294

}

295

296

@decorate(Serializable)

297

class DataModel {

298

@decorate(Computed)

299

id: number = 0;

300

301

@decorate(Computed)

302

createdAt: Date = new Date();

303

304

@decorate(Cached)

305

expensiveCalculation(input: number): number {

306

console.log("Performing expensive calculation...");

307

return input * input * input;

308

}

309

}

310

311

@decorate(Serializable)

312

class ExtendedModel {

313

@decorate(Computed)

314

updatedAt: Date = new Date();

315

316

@decorate(Cached)

317

anotherExpensiveCalc(a: number, b: number): number {

318

console.log("Another expensive calculation...");

319

return Math.pow(a, b);

320

}

321

}

322

323

class CompleteModel extends Mixin(DataModel, ExtendedModel) {

324

name: string = "Complete Model";

325

326

summary(): string {

327

return `${this.name} created at ${this.createdAt}`;

328

}

329

}

330

331

const model = new CompleteModel();

332

console.log(model.serialize()); // Serializable decorator works

333

console.log(model.expensiveCalculation(5)); // Cached decorator works

334

console.log(model.expensiveCalculation(5)); // Returns cached result

335

```

336

337

## Configuration

338

339

### Decorator Inheritance Strategy

340

341

The decorator inheritance behavior can be configured via `settings.decoratorInheritance`:

342

343

```typescript

344

import { settings } from "ts-mixer";

345

346

// Configure decorator inheritance strategy

347

settings.decoratorInheritance = "deep"; // default - inherit from all ancestors

348

settings.decoratorInheritance = "direct"; // only from direct mixin classes

349

settings.decoratorInheritance = "none"; // disable decorator inheritance

350

```

351

352

**Strategies:**

353

354

- **`"deep"`** (default): Inherits decorators from all classes in the mixin hierarchy

355

- **`"direct"`**: Only inherits decorators from classes directly passed to `Mixin()`

356

- **`"none"`**: Disables decorator inheritance completely

357

358

```typescript

359

import { Mixin, decorate, settings } from "ts-mixer";

360

361

@decorate(SomeDecorator())

362

class A {}

363

364

@decorate(AnotherDecorator())

365

class B extends A {}

366

367

@decorate(ThirdDecorator())

368

class C {}

369

370

settings.decoratorInheritance = "deep";

371

class DeepMixed extends Mixin(B, C) {}

372

// Inherits: SomeDecorator (from A), AnotherDecorator (from B), ThirdDecorator (from C)

373

374

settings.decoratorInheritance = "direct";

375

class DirectMixed extends Mixin(B, C) {}

376

// Inherits: AnotherDecorator (from B), ThirdDecorator (from C)

377

// Does NOT inherit SomeDecorator from A

378

379

settings.decoratorInheritance = "none";

380

class NoDecorators extends Mixin(B, C) {}

381

// Inherits: No decorators

382

```

383

384

## Limitations

385

386

### ES6 Map Requirement

387

388

The decorator system requires ES6 `Map` support. For older environments, you need a polyfill.

389

390

### Decorator Execution Order

391

392

When multiple classes provide decorators for the same target, they are applied in the order the classes are provided to `Mixin()`.

393

394

### Static vs Instance Context

395

396

Property and method decorators are correctly categorized as static or instance-level and applied to the appropriate context in the mixed class.

397

398

### Performance Considerations

399

400

Decorator inheritance adds some overhead during class creation. For performance-critical applications, consider using `"direct"` or `"none"` inheritance strategies.