or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-source.mdentity-definition.mdentity-schema.mdevents.mdfind-options.mdindex.mdmigrations.mdquery-builder.mdrelationships.mdrepository.md

events.mddocs/

0

# Event System & Subscribers

1

2

Comprehensive entity lifecycle event system with hooks and subscribers for implementing cross-cutting concerns like auditing, validation, and caching. TypeORM provides both decorator-based entity listeners and class-based subscribers.

3

4

## Capabilities

5

6

### Entity Lifecycle Decorators

7

8

Decorators for defining entity lifecycle event handlers directly on entity methods.

9

10

```typescript { .api }

11

/**

12

* Executed before entity is inserted into database

13

*/

14

function BeforeInsert(): MethodDecorator;

15

16

/**

17

* Executed after entity is inserted into database

18

*/

19

function AfterInsert(): MethodDecorator;

20

21

/**

22

* Executed before entity is updated in database

23

*/

24

function BeforeUpdate(): MethodDecorator;

25

26

/**

27

* Executed after entity is updated in database

28

*/

29

function AfterUpdate(): MethodDecorator;

30

31

/**

32

* Executed before entity is removed from database

33

*/

34

function BeforeRemove(): MethodDecorator;

35

36

/**

37

* Executed after entity is removed from database

38

*/

39

function AfterRemove(): MethodDecorator;

40

41

/**

42

* Executed before entity is soft removed (DeleteDateColumn set)

43

*/

44

function BeforeSoftRemove(): MethodDecorator;

45

46

/**

47

* Executed after entity is soft removed

48

*/

49

function AfterSoftRemove(): MethodDecorator;

50

51

/**

52

* Executed before soft-deleted entity is recovered

53

*/

54

function BeforeRecover(): MethodDecorator;

55

56

/**

57

* Executed after soft-deleted entity is recovered

58

*/

59

function AfterRecover(): MethodDecorator;

60

61

/**

62

* Executed after entity is loaded from database

63

*/

64

function AfterLoad(): MethodDecorator;

65

```

66

67

**Entity Lifecycle Examples:**

68

69

```typescript

70

import {

71

Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn,

72

BeforeInsert, AfterInsert, BeforeUpdate, AfterUpdate, AfterLoad

73

} from "typeorm";

74

75

@Entity()

76

export class User {

77

@PrimaryGeneratedColumn()

78

id: number;

79

80

@Column()

81

email: string;

82

83

@Column()

84

passwordHash: string;

85

86

@CreateDateColumn()

87

createdAt: Date;

88

89

@UpdateDateColumn()

90

updatedAt: Date;

91

92

@Column({ nullable: true })

93

lastLoginAt: Date;

94

95

// Computed property

96

fullName: string;

97

98

@BeforeInsert()

99

beforeInsert() {

100

console.log("About to insert user:", this.email);

101

// Hash password, validate data, etc.

102

this.email = this.email.toLowerCase();

103

}

104

105

@AfterInsert()

106

afterInsert() {

107

console.log("User inserted with ID:", this.id);

108

// Send welcome email, create audit log, etc.

109

}

110

111

@BeforeUpdate()

112

beforeUpdate() {

113

console.log("About to update user:", this.id);

114

// Validate changes, update modified fields, etc.

115

this.email = this.email.toLowerCase();

116

}

117

118

@AfterUpdate()

119

afterUpdate() {

120

console.log("User updated:", this.id);

121

// Clear cache, send notifications, etc.

122

}

123

124

@AfterLoad()

125

afterLoad() {

126

// Compute derived properties after loading from database

127

this.fullName = `${this.firstName} ${this.lastName}`;

128

}

129

}

130

```

131

132

### Entity Subscriber Interface

133

134

Interface for creating global event subscribers that can listen to events across all entities or specific entities.

135

136

```typescript { .api }

137

/**

138

* Interface for entity event subscribers

139

* @template Entity - Optional entity type to listen to

140

*/

141

interface EntitySubscriberInterface<Entity = any> {

142

/**

143

* Specifies which entity this subscriber listens to

144

* @returns Entity constructor, schema, or string name

145

*/

146

listenTo?(): ObjectType<Entity> | EntitySchema<Entity> | string;

147

148

/**

149

* Called after entity is loaded from database

150

* @param entity - Loaded entity

151

* @param event - Load event details

152

*/

153

afterLoad?(entity: Entity, event?: LoadEvent<Entity>): Promise<any> | void;

154

155

/**

156

* Called before entity is inserted

157

* @param event - Insert event details

158

*/

159

beforeInsert?(event: InsertEvent<Entity>): Promise<any> | void;

160

161

/**

162

* Called after entity is inserted

163

* @param event - Insert event details

164

*/

165

afterInsert?(event: InsertEvent<Entity>): Promise<any> | void;

166

167

/**

168

* Called before entity is updated

169

* @param event - Update event details

170

*/

171

beforeUpdate?(event: UpdateEvent<Entity>): Promise<any> | void;

172

173

/**

174

* Called after entity is updated

175

* @param event - Update event details

176

*/

177

afterUpdate?(event: UpdateEvent<Entity>): Promise<any> | void;

178

179

/**

180

* Called before entity is removed

181

* @param event - Remove event details

182

*/

183

beforeRemove?(event: RemoveEvent<Entity>): Promise<any> | void;

184

185

/**

186

* Called after entity is removed

187

* @param event - Remove event details

188

*/

189

afterRemove?(event: RemoveEvent<Entity>): Promise<any> | void;

190

191

/**

192

* Called before entity is soft removed

193

* @param event - Soft remove event details

194

*/

195

beforeSoftRemove?(event: SoftRemoveEvent<Entity>): Promise<any> | void;

196

197

/**

198

* Called after entity is soft removed

199

* @param event - Soft remove event details

200

*/

201

afterSoftRemove?(event: SoftRemoveEvent<Entity>): Promise<any> | void;

202

203

/**

204

* Called before soft-deleted entity is recovered

205

* @param event - Recover event details

206

*/

207

beforeRecover?(event: RecoverEvent<Entity>): Promise<any> | void;

208

209

/**

210

* Called after soft-deleted entity is recovered

211

* @param event - Recover event details

212

*/

213

afterRecover?(event: RecoverEvent<Entity>): Promise<any> | void;

214

215

/**

216

* Called before transaction starts

217

* @param event - Transaction start event details

218

*/

219

beforeTransactionStart?(event: TransactionStartEvent): Promise<any> | void;

220

221

/**

222

* Called after transaction starts

223

* @param event - Transaction start event details

224

*/

225

afterTransactionStart?(event: TransactionStartEvent): Promise<any> | void;

226

227

/**

228

* Called before transaction commits

229

* @param event - Transaction commit event details

230

*/

231

beforeTransactionCommit?(event: TransactionCommitEvent): Promise<any> | void;

232

233

/**

234

* Called after transaction commits

235

* @param event - Transaction commit event details

236

*/

237

afterTransactionCommit?(event: TransactionCommitEvent): Promise<any> | void;

238

239

/**

240

* Called before transaction rolls back

241

* @param event - Transaction rollback event details

242

*/

243

beforeTransactionRollback?(event: TransactionRollbackEvent): Promise<any> | void;

244

245

/**

246

* Called after transaction rolls back

247

* @param event - Transaction rollback event details

248

*/

249

afterTransactionRollback?(event: TransactionRollbackEvent): Promise<any> | void;

250

}

251

252

/**

253

* Marks a class as an entity subscriber

254

*/

255

function EventSubscriber(): ClassDecorator;

256

```

257

258

### Event Interfaces

259

260

Detailed interfaces for different types of entity events.

261

262

```typescript { .api }

263

/**

264

* Event fired when entity is loaded from database

265

*/

266

interface LoadEvent<Entity> {

267

/** Connection where event occurred */

268

connection: DataSource;

269

/** Query runner executing the operation */

270

queryRunner?: QueryRunner;

271

/** Entity manager */

272

manager: EntityManager;

273

/** Loaded entity */

274

entity: Entity;

275

}

276

277

/**

278

* Event fired during entity insert operations

279

*/

280

interface InsertEvent<Entity> {

281

/** Connection where event occurred */

282

connection: DataSource;

283

/** Query runner executing the operation */

284

queryRunner?: QueryRunner;

285

/** Entity manager */

286

manager: EntityManager;

287

/** Entity being inserted */

288

entity: Entity;

289

}

290

291

/**

292

* Event fired during entity update operations

293

*/

294

interface UpdateEvent<Entity> {

295

/** Connection where event occurred */

296

connection: DataSource;

297

/** Query runner executing the operation */

298

queryRunner?: QueryRunner;

299

/** Entity manager */

300

manager: EntityManager;

301

/** Entity being updated */

302

entity: Entity;

303

/** Database entity state (before update) */

304

databaseEntity: Entity;

305

/** Changed columns */

306

updatedColumns: ColumnMetadata[];

307

/** Changed relations */

308

updatedRelations: RelationMetadata[];

309

}

310

311

/**

312

* Event fired during entity remove operations

313

*/

314

interface RemoveEvent<Entity> {

315

/** Connection where event occurred */

316

connection: DataSource;

317

/** Query runner executing the operation */

318

queryRunner?: QueryRunner;

319

/** Entity manager */

320

manager: EntityManager;

321

/** Entity being removed */

322

entity: Entity;

323

/** Entity ID being removed */

324

entityId: any;

325

/** Database entity state */

326

databaseEntity: Entity;

327

}

328

329

/**

330

* Event fired during soft remove operations

331

*/

332

interface SoftRemoveEvent<Entity> {

333

/** Connection where event occurred */

334

connection: DataSource;

335

/** Query runner executing the operation */

336

queryRunner?: QueryRunner;

337

/** Entity manager */

338

manager: EntityManager;

339

/** Entity being soft removed */

340

entity: Entity;

341

/** Database entity state */

342

databaseEntity: Entity;

343

}

344

345

/**

346

* Event fired during entity recovery operations

347

*/

348

interface RecoverEvent<Entity> {

349

/** Connection where event occurred */

350

connection: DataSource;

351

/** Query runner executing the operation */

352

queryRunner?: QueryRunner;

353

/** Entity manager */

354

manager: EntityManager;

355

/** Entity being recovered */

356

entity: Entity;

357

/** Database entity state */

358

databaseEntity: Entity;

359

}

360

361

/**

362

* Event fired when database transaction starts

363

*/

364

interface TransactionStartEvent {

365

/** Connection where event occurred */

366

connection: DataSource;

367

/** Query runner executing the transaction */

368

queryRunner: QueryRunner;

369

}

370

371

/**

372

* Event fired when database transaction commits

373

*/

374

interface TransactionCommitEvent {

375

/** Connection where event occurred */

376

connection: DataSource;

377

/** Query runner executing the transaction */

378

queryRunner: QueryRunner;

379

}

380

381

/**

382

* Event fired when database transaction rolls back

383

*/

384

interface TransactionRollbackEvent {

385

/** Connection where event occurred */

386

connection: DataSource;

387

/** Query runner executing the transaction */

388

queryRunner: QueryRunner;

389

}

390

```

391

392

**Entity Subscriber Examples:**

393

394

```typescript

395

import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";

396

397

// Global subscriber listening to all entities

398

@EventSubscriber()

399

export class GlobalAuditSubscriber implements EntitySubscriberInterface {

400

beforeInsert(event: InsertEvent<any>) {

401

console.log(`Inserting entity: ${event.entity.constructor.name}`);

402

// Log to audit system

403

}

404

405

beforeUpdate(event: UpdateEvent<any>) {

406

console.log(`Updating entity: ${event.entity.constructor.name}`);

407

// Compare changes and log

408

console.log("Changed columns:", event.updatedColumns.map(col => col.propertyName));

409

}

410

}

411

412

// Subscriber for specific entity

413

@EventSubscriber()

414

export class UserSubscriber implements EntitySubscriberInterface<User> {

415

listenTo() {

416

return User;

417

}

418

419

beforeInsert(event: InsertEvent<User>) {

420

// Hash password before insert

421

if (event.entity.password) {

422

event.entity.passwordHash = hashPassword(event.entity.password);

423

delete event.entity.password;

424

}

425

}

426

427

afterInsert(event: InsertEvent<User>) {

428

// Send welcome email

429

sendWelcomeEmail(event.entity.email);

430

431

// Create user profile

432

const profile = new Profile();

433

profile.user = event.entity;

434

return event.manager.save(Profile, profile);

435

}

436

437

beforeUpdate(event: UpdateEvent<User>) {

438

// Check if email changed

439

if (event.entity.email !== event.databaseEntity.email) {

440

event.entity.emailVerified = false;

441

}

442

}

443

444

afterUpdate(event: UpdateEvent<User>) {

445

// Clear user cache

446

clearUserCache(event.entity.id);

447

448

// Send notification if important field changed

449

const importantFields = ['email', 'role'];

450

const changedImportantFields = event.updatedColumns

451

.map(col => col.propertyName)

452

.filter(field => importantFields.includes(field));

453

454

if (changedImportantFields.length > 0) {

455

sendAccountChangeNotification(event.entity.email, changedImportantFields);

456

}

457

}

458

459

beforeRemove(event: RemoveEvent<User>) {

460

// Archive user data before deletion

461

archiveUserData(event.entity);

462

}

463

}

464

465

// Transaction subscriber

466

@EventSubscriber()

467

export class TransactionSubscriber implements EntitySubscriberInterface {

468

afterTransactionCommit(event: TransactionCommitEvent) {

469

// Clear cache, send events, etc.

470

console.log("Transaction committed successfully");

471

}

472

473

afterTransactionRollback(event: TransactionRollbackEvent) {

474

// Log error, send alerts, etc.

475

console.log("Transaction rolled back");

476

}

477

}

478

```

479

480

### Advanced Event Patterns

481

482

Complex event handling patterns for enterprise applications.

483

484

```typescript

485

// Event aggregation subscriber

486

@EventSubscriber()

487

export class EventAggregatorSubscriber implements EntitySubscriberInterface {

488

private events: any[] = [];

489

490

afterInsert(event: InsertEvent<any>) {

491

this.events.push({

492

type: 'INSERT',

493

entity: event.entity.constructor.name,

494

id: event.entity.id,

495

timestamp: new Date()

496

});

497

}

498

499

afterUpdate(event: UpdateEvent<any>) {

500

this.events.push({

501

type: 'UPDATE',

502

entity: event.entity.constructor.name,

503

id: event.entity.id,

504

changes: event.updatedColumns.map(col => col.propertyName),

505

timestamp: new Date()

506

});

507

}

508

509

afterTransactionCommit(event: TransactionCommitEvent) {

510

// Batch send all events after successful transaction

511

if (this.events.length > 0) {

512

sendEventBatch(this.events);

513

this.events = [];

514

}

515

}

516

517

afterTransactionRollback(event: TransactionRollbackEvent) {

518

// Clear events on rollback

519

this.events = [];

520

}

521

}

522

523

// Validation subscriber

524

@EventSubscriber()

525

export class ValidationSubscriber implements EntitySubscriberInterface {

526

beforeInsert(event: InsertEvent<any>) {

527

return this.validateEntity(event.entity, event.manager);

528

}

529

530

beforeUpdate(event: UpdateEvent<any>) {

531

return this.validateEntity(event.entity, event.manager);

532

}

533

534

private async validateEntity(entity: any, manager: EntityManager) {

535

// Custom validation logic

536

const errors = [];

537

538

// Example: Check unique constraints

539

if (entity.email) {

540

const existingUser = await manager.findOneBy(entity.constructor, {

541

email: entity.email,

542

id: Not(entity.id || 0)

543

});

544

if (existingUser) {

545

errors.push('Email already exists');

546

}

547

}

548

549

if (errors.length > 0) {

550

throw new Error(`Validation failed: ${errors.join(', ')}`);

551

}

552

}

553

}

554

555

// Caching subscriber

556

@EventSubscriber()

557

export class CacheSubscriber implements EntitySubscriberInterface {

558

afterInsert(event: InsertEvent<any>) {

559

this.invalidateCache(event.entity.constructor.name);

560

}

561

562

afterUpdate(event: UpdateEvent<any>) {

563

this.invalidateCache(event.entity.constructor.name);

564

this.invalidateEntityCache(event.entity.constructor.name, event.entity.id);

565

}

566

567

afterRemove(event: RemoveEvent<any>) {

568

this.invalidateCache(event.entity.constructor.name);

569

this.invalidateEntityCache(event.entity.constructor.name, event.entityId);

570

}

571

572

private invalidateCache(entityName: string) {

573

// Clear list caches

574

cache.del(`${entityName}:list:*`);

575

}

576

577

private invalidateEntityCache(entityName: string, entityId: any) {

578

// Clear specific entity cache

579

cache.del(`${entityName}:${entityId}`);

580

}

581

}

582

```

583

584

### Event Configuration

585

586

Configure event subscribers in your DataSource:

587

588

```typescript

589

import { DataSource } from "typeorm";

590

import { UserSubscriber, GlobalAuditSubscriber } from "./subscribers";

591

592

const dataSource = new DataSource({

593

// ... other options

594

subscribers: [

595

UserSubscriber,

596

GlobalAuditSubscriber,

597

// ... other subscribers

598

],

599

});

600

601

// Or add subscribers programmatically

602

dataSource.subscribers.push(new UserSubscriber());

603

```

604

605

## Event System Use Cases

606

607

The event system is ideal for:

608

609

- **Auditing**: Track all entity changes for compliance

610

- **Validation**: Implement complex business rules

611

- **Caching**: Manage cache invalidation

612

- **Notifications**: Send emails/messages on entity changes

613

- **Data Transformation**: Modify data before/after operations

614

- **Integration**: Sync with external systems

615

- **Logging**: Track database operations

616

- **Security**: Implement access controls and monitoring

617

618

## Best Practices

619

620

### Performance Considerations

621

622

```typescript

623

@EventSubscriber()

624

export class PerformantSubscriber implements EntitySubscriberInterface {

625

// Use async operations carefully

626

async afterInsert(event: InsertEvent<any>) {

627

// Don't block the main transaction

628

setImmediate(() => {

629

this.sendNotificationAsync(event.entity);

630

});

631

}

632

633

private async sendNotificationAsync(entity: any) {

634

// Async operation that doesn't affect main transaction

635

}

636

637

// Batch operations when possible

638

private pendingOperations: any[] = [];

639

640

afterUpdate(event: UpdateEvent<any>) {

641

this.pendingOperations.push(event);

642

}

643

644

afterTransactionCommit(event: TransactionCommitEvent) {

645

if (this.pendingOperations.length > 0) {

646

this.processBatch(this.pendingOperations);

647

this.pendingOperations = [];

648

}

649

}

650

}

651

```