CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-typeorm

Data-Mapper ORM for TypeScript and ES2021+ supporting MySQL/MariaDB, PostgreSQL, MS SQL Server, Oracle, SAP HANA, SQLite, MongoDB databases.

Overview
Eval results
Files

events.mddocs/

Event System & Subscribers

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.

Capabilities

Entity Lifecycle Decorators

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

/**
 * Executed before entity is inserted into database
 */
function BeforeInsert(): MethodDecorator;

/**
 * Executed after entity is inserted into database
 */
function AfterInsert(): MethodDecorator;

/**
 * Executed before entity is updated in database
 */
function BeforeUpdate(): MethodDecorator;

/**
 * Executed after entity is updated in database
 */
function AfterUpdate(): MethodDecorator;

/**
 * Executed before entity is removed from database
 */
function BeforeRemove(): MethodDecorator;

/**
 * Executed after entity is removed from database
 */
function AfterRemove(): MethodDecorator;

/**
 * Executed before entity is soft removed (DeleteDateColumn set)
 */
function BeforeSoftRemove(): MethodDecorator;

/**
 * Executed after entity is soft removed
 */
function AfterSoftRemove(): MethodDecorator;

/**
 * Executed before soft-deleted entity is recovered
 */
function BeforeRecover(): MethodDecorator;

/**
 * Executed after soft-deleted entity is recovered
 */
function AfterRecover(): MethodDecorator;

/**
 * Executed after entity is loaded from database
 */
function AfterLoad(): MethodDecorator;

Entity Lifecycle Examples:

import {
  Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn,
  BeforeInsert, AfterInsert, BeforeUpdate, AfterUpdate, AfterLoad
} from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  email: string;

  @Column()
  passwordHash: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @Column({ nullable: true })
  lastLoginAt: Date;

  // Computed property
  fullName: string;

  @BeforeInsert()
  beforeInsert() {
    console.log("About to insert user:", this.email);
    // Hash password, validate data, etc.
    this.email = this.email.toLowerCase();
  }

  @AfterInsert()
  afterInsert() {
    console.log("User inserted with ID:", this.id);
    // Send welcome email, create audit log, etc.
  }

  @BeforeUpdate()
  beforeUpdate() {
    console.log("About to update user:", this.id);
    // Validate changes, update modified fields, etc.
    this.email = this.email.toLowerCase();
  }

  @AfterUpdate()
  afterUpdate() {
    console.log("User updated:", this.id);
    // Clear cache, send notifications, etc.
  }

  @AfterLoad()
  afterLoad() {
    // Compute derived properties after loading from database
    this.fullName = `${this.firstName} ${this.lastName}`;
  }
}

Entity Subscriber Interface

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

/**
 * Interface for entity event subscribers
 * @template Entity - Optional entity type to listen to
 */
interface EntitySubscriberInterface<Entity = any> {
  /**
   * Specifies which entity this subscriber listens to
   * @returns Entity constructor, schema, or string name
   */
  listenTo?(): ObjectType<Entity> | EntitySchema<Entity> | string;

  /**
   * Called after entity is loaded from database
   * @param entity - Loaded entity
   * @param event - Load event details
   */
  afterLoad?(entity: Entity, event?: LoadEvent<Entity>): Promise<any> | void;

  /**
   * Called before entity is inserted
   * @param event - Insert event details
   */
  beforeInsert?(event: InsertEvent<Entity>): Promise<any> | void;

  /**
   * Called after entity is inserted
   * @param event - Insert event details
   */
  afterInsert?(event: InsertEvent<Entity>): Promise<any> | void;

  /**
   * Called before entity is updated
   * @param event - Update event details
   */
  beforeUpdate?(event: UpdateEvent<Entity>): Promise<any> | void;

  /**
   * Called after entity is updated
   * @param event - Update event details
   */
  afterUpdate?(event: UpdateEvent<Entity>): Promise<any> | void;

  /**
   * Called before entity is removed
   * @param event - Remove event details
   */
  beforeRemove?(event: RemoveEvent<Entity>): Promise<any> | void;

  /**
   * Called after entity is removed
   * @param event - Remove event details
   */
  afterRemove?(event: RemoveEvent<Entity>): Promise<any> | void;

  /**
   * Called before entity is soft removed
   * @param event - Soft remove event details
   */
  beforeSoftRemove?(event: SoftRemoveEvent<Entity>): Promise<any> | void;

  /**
   * Called after entity is soft removed
   * @param event - Soft remove event details
   */
  afterSoftRemove?(event: SoftRemoveEvent<Entity>): Promise<any> | void;

  /**
   * Called before soft-deleted entity is recovered
   * @param event - Recover event details
   */
  beforeRecover?(event: RecoverEvent<Entity>): Promise<any> | void;

  /**
   * Called after soft-deleted entity is recovered
   * @param event - Recover event details
   */
  afterRecover?(event: RecoverEvent<Entity>): Promise<any> | void;

  /**
   * Called before transaction starts
   * @param event - Transaction start event details
   */
  beforeTransactionStart?(event: TransactionStartEvent): Promise<any> | void;

  /**
   * Called after transaction starts
   * @param event - Transaction start event details
   */
  afterTransactionStart?(event: TransactionStartEvent): Promise<any> | void;

  /**
   * Called before transaction commits
   * @param event - Transaction commit event details
   */
  beforeTransactionCommit?(event: TransactionCommitEvent): Promise<any> | void;

  /**
   * Called after transaction commits
   * @param event - Transaction commit event details
   */
  afterTransactionCommit?(event: TransactionCommitEvent): Promise<any> | void;

  /**
   * Called before transaction rolls back
   * @param event - Transaction rollback event details
   */
  beforeTransactionRollback?(event: TransactionRollbackEvent): Promise<any> | void;

  /**
   * Called after transaction rolls back
   * @param event - Transaction rollback event details
   */
  afterTransactionRollback?(event: TransactionRollbackEvent): Promise<any> | void;
}

/**
 * Marks a class as an entity subscriber
 */
function EventSubscriber(): ClassDecorator;

Event Interfaces

Detailed interfaces for different types of entity events.

/**
 * Event fired when entity is loaded from database
 */
interface LoadEvent<Entity> {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the operation */
  queryRunner?: QueryRunner;
  /** Entity manager */
  manager: EntityManager;
  /** Loaded entity */
  entity: Entity;
}

/**
 * Event fired during entity insert operations
 */
interface InsertEvent<Entity> {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the operation */
  queryRunner?: QueryRunner;
  /** Entity manager */
  manager: EntityManager;
  /** Entity being inserted */
  entity: Entity;
}

/**
 * Event fired during entity update operations
 */
interface UpdateEvent<Entity> {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the operation */
  queryRunner?: QueryRunner;
  /** Entity manager */
  manager: EntityManager;
  /** Entity being updated */
  entity: Entity;
  /** Database entity state (before update) */
  databaseEntity: Entity;
  /** Changed columns */
  updatedColumns: ColumnMetadata[];
  /** Changed relations */
  updatedRelations: RelationMetadata[];
}

/**
 * Event fired during entity remove operations
 */
interface RemoveEvent<Entity> {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the operation */
  queryRunner?: QueryRunner;
  /** Entity manager */
  manager: EntityManager;
  /** Entity being removed */
  entity: Entity;
  /** Entity ID being removed */
  entityId: any;
  /** Database entity state */
  databaseEntity: Entity;
}

/**
 * Event fired during soft remove operations
 */
interface SoftRemoveEvent<Entity> {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the operation */
  queryRunner?: QueryRunner;
  /** Entity manager */
  manager: EntityManager;
  /** Entity being soft removed */
  entity: Entity;
  /** Database entity state */
  databaseEntity: Entity;
}

/**
 * Event fired during entity recovery operations
 */
interface RecoverEvent<Entity> {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the operation */
  queryRunner?: QueryRunner;
  /** Entity manager */
  manager: EntityManager;
  /** Entity being recovered */
  entity: Entity;
  /** Database entity state */
  databaseEntity: Entity;
}

/**
 * Event fired when database transaction starts
 */
interface TransactionStartEvent {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the transaction */
  queryRunner: QueryRunner;
}

/**
 * Event fired when database transaction commits
 */
interface TransactionCommitEvent {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the transaction */
  queryRunner: QueryRunner;
}

/**
 * Event fired when database transaction rolls back
 */
interface TransactionRollbackEvent {
  /** Connection where event occurred */
  connection: DataSource;
  /** Query runner executing the transaction */
  queryRunner: QueryRunner;
}

Entity Subscriber Examples:

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

// Global subscriber listening to all entities
@EventSubscriber()
export class GlobalAuditSubscriber implements EntitySubscriberInterface {
  beforeInsert(event: InsertEvent<any>) {
    console.log(`Inserting entity: ${event.entity.constructor.name}`);
    // Log to audit system
  }

  beforeUpdate(event: UpdateEvent<any>) {
    console.log(`Updating entity: ${event.entity.constructor.name}`);
    // Compare changes and log
    console.log("Changed columns:", event.updatedColumns.map(col => col.propertyName));
  }
}

// Subscriber for specific entity
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
  listenTo() {
    return User;
  }

  beforeInsert(event: InsertEvent<User>) {
    // Hash password before insert
    if (event.entity.password) {
      event.entity.passwordHash = hashPassword(event.entity.password);
      delete event.entity.password;
    }
  }

  afterInsert(event: InsertEvent<User>) {
    // Send welcome email
    sendWelcomeEmail(event.entity.email);

    // Create user profile
    const profile = new Profile();
    profile.user = event.entity;
    return event.manager.save(Profile, profile);
  }

  beforeUpdate(event: UpdateEvent<User>) {
    // Check if email changed
    if (event.entity.email !== event.databaseEntity.email) {
      event.entity.emailVerified = false;
    }
  }

  afterUpdate(event: UpdateEvent<User>) {
    // Clear user cache
    clearUserCache(event.entity.id);

    // Send notification if important field changed
    const importantFields = ['email', 'role'];
    const changedImportantFields = event.updatedColumns
      .map(col => col.propertyName)
      .filter(field => importantFields.includes(field));

    if (changedImportantFields.length > 0) {
      sendAccountChangeNotification(event.entity.email, changedImportantFields);
    }
  }

  beforeRemove(event: RemoveEvent<User>) {
    // Archive user data before deletion
    archiveUserData(event.entity);
  }
}

// Transaction subscriber
@EventSubscriber()
export class TransactionSubscriber implements EntitySubscriberInterface {
  afterTransactionCommit(event: TransactionCommitEvent) {
    // Clear cache, send events, etc.
    console.log("Transaction committed successfully");
  }

  afterTransactionRollback(event: TransactionRollbackEvent) {
    // Log error, send alerts, etc.
    console.log("Transaction rolled back");
  }
}

Advanced Event Patterns

Complex event handling patterns for enterprise applications.

// Event aggregation subscriber
@EventSubscriber()
export class EventAggregatorSubscriber implements EntitySubscriberInterface {
  private events: any[] = [];

  afterInsert(event: InsertEvent<any>) {
    this.events.push({
      type: 'INSERT',
      entity: event.entity.constructor.name,
      id: event.entity.id,
      timestamp: new Date()
    });
  }

  afterUpdate(event: UpdateEvent<any>) {
    this.events.push({
      type: 'UPDATE',
      entity: event.entity.constructor.name,
      id: event.entity.id,
      changes: event.updatedColumns.map(col => col.propertyName),
      timestamp: new Date()
    });
  }

  afterTransactionCommit(event: TransactionCommitEvent) {
    // Batch send all events after successful transaction
    if (this.events.length > 0) {
      sendEventBatch(this.events);
      this.events = [];
    }
  }

  afterTransactionRollback(event: TransactionRollbackEvent) {
    // Clear events on rollback
    this.events = [];
  }
}

// Validation subscriber
@EventSubscriber()
export class ValidationSubscriber implements EntitySubscriberInterface {
  beforeInsert(event: InsertEvent<any>) {
    return this.validateEntity(event.entity, event.manager);
  }

  beforeUpdate(event: UpdateEvent<any>) {
    return this.validateEntity(event.entity, event.manager);
  }

  private async validateEntity(entity: any, manager: EntityManager) {
    // Custom validation logic
    const errors = [];

    // Example: Check unique constraints
    if (entity.email) {
      const existingUser = await manager.findOneBy(entity.constructor, {
        email: entity.email,
        id: Not(entity.id || 0)
      });
      if (existingUser) {
        errors.push('Email already exists');
      }
    }

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

// Caching subscriber
@EventSubscriber()
export class CacheSubscriber implements EntitySubscriberInterface {
  afterInsert(event: InsertEvent<any>) {
    this.invalidateCache(event.entity.constructor.name);
  }

  afterUpdate(event: UpdateEvent<any>) {
    this.invalidateCache(event.entity.constructor.name);
    this.invalidateEntityCache(event.entity.constructor.name, event.entity.id);
  }

  afterRemove(event: RemoveEvent<any>) {
    this.invalidateCache(event.entity.constructor.name);
    this.invalidateEntityCache(event.entity.constructor.name, event.entityId);
  }

  private invalidateCache(entityName: string) {
    // Clear list caches
    cache.del(`${entityName}:list:*`);
  }

  private invalidateEntityCache(entityName: string, entityId: any) {
    // Clear specific entity cache
    cache.del(`${entityName}:${entityId}`);
  }
}

Event Configuration

Configure event subscribers in your DataSource:

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

const dataSource = new DataSource({
  // ... other options
  subscribers: [
    UserSubscriber,
    GlobalAuditSubscriber,
    // ... other subscribers
  ],
});

// Or add subscribers programmatically
dataSource.subscribers.push(new UserSubscriber());

Event System Use Cases

The event system is ideal for:

  • Auditing: Track all entity changes for compliance
  • Validation: Implement complex business rules
  • Caching: Manage cache invalidation
  • Notifications: Send emails/messages on entity changes
  • Data Transformation: Modify data before/after operations
  • Integration: Sync with external systems
  • Logging: Track database operations
  • Security: Implement access controls and monitoring

Best Practices

Performance Considerations

@EventSubscriber()
export class PerformantSubscriber implements EntitySubscriberInterface {
  // Use async operations carefully
  async afterInsert(event: InsertEvent<any>) {
    // Don't block the main transaction
    setImmediate(() => {
      this.sendNotificationAsync(event.entity);
    });
  }

  private async sendNotificationAsync(entity: any) {
    // Async operation that doesn't affect main transaction
  }

  // Batch operations when possible
  private pendingOperations: any[] = [];

  afterUpdate(event: UpdateEvent<any>) {
    this.pendingOperations.push(event);
  }

  afterTransactionCommit(event: TransactionCommitEvent) {
    if (this.pendingOperations.length > 0) {
      this.processBatch(this.pendingOperations);
      this.pendingOperations = [];
    }
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-typeorm

docs

data-source.md

entity-definition.md

entity-schema.md

events.md

find-options.md

index.md

migrations.md

query-builder.md

relationships.md

repository.md

tile.json