Skip to content

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

EventPublisherSubscribers
identity.user_registeredidentityeconomy (bootstrap wallet)
identity.invite_code.usedidentityreputation (+3 to inviter), invite-tree
interests.user_selectedinterestsreputation (+5 per interest)
interests.depth_changedinterests
posts.createdpostsreputation (+3 to author), contribution
posts.repliedpostsreputation (+2 to replier), notifications
posts.reactedpostsreputation (+1 per unique reactor), notifications
posts.bookmarkedpostsreputation (+1 per unique bookmarker)
posts.repostedpostsreputation (+1 per unique reposter)
emoji_pay.senteconomynotifications, contribution
circle.invitedsocialnotifications
circle.member_joinedsocial
reputation.changedreputationnotifications
message.postedmessagingnotifications
wiki.proposal.approvedwikinotifications

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.

Regulus — invite-only social-knowledge platform