TypeORM Subscribers
Recommendation (New Standard)
In SolidX, TypeORM subscriber hooks should always publish a background job and return quickly.
Do not execute heavy business side effects directly inside afterInsert, afterUpdate, etc.
Preferred flow:
- TypeORM subscriber hook runs (
afterInsert,afterUpdate, ...) - Hook publishes a queue message through
PublisherFactory - Queue subscriber processes the actual business logic asynchronously
This publisher-subscriber pair should be your default pattern for subscriber-driven side effects.
Why This Is Recommended
This pattern avoids common failure and consistency problems:
- Avoids long-running work inside DB lifecycle hooks
- Reduces risk around transaction boundaries and lock duration
- Gives retries/failure tracking through queue infrastructure
- Improves reliability for external side effects (email/SMS/webhooks/3rd-party APIs)
- Keeps entity lifecycle hooks deterministic and lightweight
Anti-Pattern to Avoid
Avoid this in TypeORM subscriber hooks:
- External API calls
- File processing/OCR/LLM calls
- Large cross-entity updates
- Long computations
Do these in queue subscribers instead.
Implementation Pattern
1) Queue payload and options
import { BrokerType } from "@solidxai/core";
export interface UserChangedPayload {
userId: number;
eventType: "created" | "updated";
}
export default {
name: "userChangedQueueRabbitmq",
type: BrokerType.RabbitMQ,
queueName: "user_changed_queue",
prefetch: 5,
};
2) Publisher
import { Injectable } from "@nestjs/common";
import {
MqMessageQueueService,
MqMessageService,
QueuesModuleOptions,
RabbitMqPublisher,
} from "@solidxai/core";
import userChangedQueueOptions, { UserChangedPayload } from "./user-changed-queue-options";
@Injectable()
export class UserChangedPublisher extends RabbitMqPublisher<UserChangedPayload> {
constructor(
protected readonly mqMessageService: MqMessageService,
protected readonly mqMessageQueueService: MqMessageQueueService
) {
super(mqMessageService, mqMessageQueueService);
}
options(): QueuesModuleOptions {
return {
...userChangedQueueOptions,
};
}
}
3) TypeORM subscriber (hook only publishes via PublisherFactory)
import { Injectable } from "@nestjs/common";
import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";
import { PublisherFactory } from "@solidxai/core";
import { User } from "../entities/user.entity";
import { UserChangedPayload } from "../background-jobs/user-changed-queue-options";
@Injectable()
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
constructor(
private readonly publisherFactory: PublisherFactory<UserChangedPayload>
) {}
listenTo() {
return User;
}
async afterInsert(event: InsertEvent<User>) {
if (!event.entity?.id) return;
await this.publisherFactory.publish(
{
payload: {
userId: event.entity.id,
eventType: "created",
},
parentEntity: "user",
parentEntityId: event.entity.id,
},
"UserChangedPublisher"
);
}
async afterUpdate(event: UpdateEvent<User>) {
const id = (event.entity as User | undefined)?.id;
if (!id) return;
await this.publisherFactory.publish(
{
payload: {
userId: id,
eventType: "updated",
},
parentEntity: "user",
parentEntityId: id,
},
"UserChangedPublisher"
);
}
}
4) Queue subscriber (business logic runs here)
import { Injectable } from "@nestjs/common";
import {
MqMessageQueueService,
MqMessageService,
QueueMessage,
QueuesModuleOptions,
RabbitMqSubscriber,
} from "@solidxai/core";
import userChangedQueueOptions, { UserChangedPayload } from "./user-changed-queue-options";
@Injectable()
export class UserChangedJobSubscriber extends RabbitMqSubscriber<UserChangedPayload> {
constructor(
readonly mqMessageService: MqMessageService,
readonly mqMessageQueueService: MqMessageQueueService
) {
super(mqMessageService, mqMessageQueueService);
}
options(): QueuesModuleOptions {
return {
...userChangedQueueOptions,
};
}
async subscribe(message: QueueMessage<UserChangedPayload>) {
const { userId, eventType } = message.payload;
// Real side effects: notify, sync, call external APIs, etc.
return { success: true, userId, eventType };
}
}
Practical Rules
- Keep TypeORM hook methods minimal and side-effect free except queue publish.
- Publish via
PublisherFactory; do not inject broker-specific publisher classes in subscriber hooks. - Prefer logical publisher names (
UserChangedPublisher) and let the factory resolve broker-specific implementations. - Pass identifiers and minimal context in queue payloads.
- Do not pass full mutable entity graphs in queue messages.
- Keep retry/error logic in queue subscriber infrastructure.
- Use
prefetchand worker replicas to scale processing throughput.
Registration
Register TypeORM subscriber + publisher classes + queue subscriber classes as providers in your module.
Publishing call sites should depend on PublisherFactory.
Notes
- You can still use TypeORM hooks for lightweight in-memory data preparation, but not heavy side effects.
- If in doubt, publish and process asynchronously.