Event Bus
Regulus uses NestJS EventEmitter2 as the in-process event bus. All cross-bounded-context communication flows through events — direct service imports across module boundaries are forbidden.
Pattern
typescript
// Publisher (posts module)
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class PostsService {
constructor(private readonly events: EventEmitter2) {}
async createPost(userId: string, dto: CreatePostDto): Promise<Post> {
const post = await this.prisma.post.create({ data: { ... } });
await this.events.emitAsync('posts.created', {
postId: post.id,
authorId: post.authorId,
interestId: post.interestId,
});
return post;
}
}
// Subscriber (reputation module)
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class ReputationService {
@OnEvent('posts.created')
async handlePostCreated(event: { postId: string; authorId: string; interestId: string | null }) {
if (event.interestId) {
await this.record({
userId: event.authorId,
interestId: event.interestId,
delta: +3,
reason: 'posts.created',
});
}
}
}Event Catalogue
| Event | Publisher | Subscribers |
|---|---|---|
identity.user_registered | identity | economy (bootstrap wallet) |
identity.invite_code.used | identity | reputation (+3 to inviter), invite-tree |
interests.user_selected | interests | reputation (+5 per interest) |
interests.depth_changed | interests | — |
posts.created | posts | reputation (+3 to author), contribution |
posts.replied | posts | reputation (+2 to replier), notifications |
posts.reacted | posts | reputation (+1 per unique reactor), notifications |
posts.bookmarked | posts | reputation (+1 per unique bookmarker) |
posts.reposted | posts | reputation (+1 per unique reposter) |
emoji_pay.sent | economy | notifications, contribution |
circle.invited | social | notifications |
circle.member_joined | social | — |
reputation.changed | reputation | notifications |
message.posted | messaging | notifications |
wiki.proposal.approved | wiki | notifications |
Type Safety
Event payloads are typed in packages/domain/src/events/. Always import from @regulus/domain — never re-define event shapes inline.
typescript
// packages/domain/src/events/posts.events.ts
export class PostCreatedEvent {
constructor(public readonly payload: {
postId: string;
authorId: string;
interestId: string | null;
format: string;
}) {}
}Error Handling
emitAsync is used for all events with side-effects. Errors in subscribers are caught and logged — they do not roll back the publisher's transaction. For critical cross-context consistency (e.g. wallet bootstrap), use emitAsync and await before responding.