@nestbolt/likeable
Events
Listen to like and unlike events via @nestjs/event-emitter.
@nestbolt/likeable emits events when entities are liked or unliked. Events are dispatched via @nestjs/event-emitter when it's installed and EventEmitterModule is registered.
Setup
Install and register the event emitter module:
npm install @nestjs/event-emitterimport { EventEmitterModule } from "@nestjs/event-emitter";
import { LikeableModule } from "@nestbolt/likeable";
@Module({
imports: [
EventEmitterModule.forRoot(),
LikeableModule.forRoot(),
// ...
],
})
export class AppModule {}If @nestjs/event-emitter is not installed or EventEmitterModule is not registered, the package works normally but no events are emitted. The event emitter is resolved lazily and treated as fully optional.
Events
like.liked
Emitted after a successful like() call -- but only when an actual row was inserted. Duplicate likes (idempotent no-ops) do not emit.
import { OnEvent } from "@nestjs/event-emitter";
import { LIKEABLE_EVENTS, type LikedEvent } from "@nestbolt/likeable";
@Injectable()
export class LikeListener {
@OnEvent(LIKEABLE_EVENTS.LIKED)
handleLiked(event: LikedEvent) {
console.log(`User ${event.userId} liked ${event.likeableType}:${event.likeableId}`);
}
}Payload: LikedEvent
| Field | Type | Description |
|---|---|---|
likeableType | string | The polymorphic type of the entity (from @Likeable({ type }) or class name) |
likeableId | string | The ID of the entity that was liked |
userId | string | The ID of the user who liked it |
like.unliked
Emitted after a successful unlike() call -- but only when a row was actually deleted. Calling unlike() for a user who hadn't liked the entity is a no-op and does not emit.
import { OnEvent } from "@nestjs/event-emitter";
import { LIKEABLE_EVENTS, type UnlikedEvent } from "@nestbolt/likeable";
@Injectable()
export class LikeListener {
@OnEvent(LIKEABLE_EVENTS.UNLIKED)
handleUnliked(event: UnlikedEvent) {
console.log(`User ${event.userId} unliked ${event.likeableType}:${event.likeableId}`);
}
}Payload: UnlikedEvent
| Field | Type | Description |
|---|---|---|
likeableType | string | The polymorphic type of the entity |
likeableId | string | The ID of the entity that was unliked |
userId | string | The ID of the user who removed their like |
Event Constants
Use the LIKEABLE_EVENTS constant for type-safe event names:
import { LIKEABLE_EVENTS } from "@nestbolt/likeable";
LIKEABLE_EVENTS.LIKED; // "like.liked"
LIKEABLE_EVENTS.UNLIKED; // "like.unliked"Behavior with toggle()
toggle() is implemented in terms of like() and unlike(), so it emits exactly one of the two events per call -- whichever side of the toggle actually changed state:
await post.toggle(userId); // emits like.liked
await post.toggle(userId); // emits like.unlikedPractical Example: Notify the Author
A common use case is notifying the author of a post when someone likes it:
@Injectable()
export class LikeNotificationListener {
constructor(
@InjectRepository(Post) private readonly posts: Repository<Post>,
private readonly notifications: NotificationsService,
) {}
@OnEvent(LIKEABLE_EVENTS.LIKED)
async handleLiked(event: LikedEvent) {
if (event.likeableType !== "Post") return;
const post = await this.posts.findOneBy({ id: event.likeableId });
if (!post || post.authorId === event.userId) return;
await this.notifications.send(post.authorId, {
type: "post.liked",
postId: post.id,
likedBy: event.userId,
});
}
}Filter on likeableType so a single listener can handle one entity type at a time, even though all likes share the like.liked event name.
Reliability
Events are emitted after the database write succeeds. If your handler throws, the like itself is unaffected -- the row is already committed. Events are best-effort signals, not transactional outbox messages.