Marketplace blueprinteventbus-integration
Implement event-driven communication using BlueprintEventBus for cross-module coordination. Use this skill when modules need to communicate without tight coupling, broadcasting domain events (task.created, member.added), subscribing to events with proper lifecycle management, and implementing event-driven workflows. Ensures events follow naming conventions ([module].[action]), include Blueprint context, and use takeUntilDestroyed() for automatic cleanup.
install
source · Clone the upstream repo
git clone https://github.com/aiskillstore/marketplace
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/7spade/blueprinteventbus-integration" ~/.claude/skills/aiskillstore-marketplace-blueprinteventbus-integration && rm -rf "$T"
manifest:
skills/7spade/blueprinteventbus-integration/SKILL.mdsource content
BlueprintEventBus Integration Skill
This skill helps implement event-driven architecture using BlueprintEventBus.
Core Principles
Event-Driven Architecture
- Decoupling: Modules communicate via events, not direct calls
- Blueprint Context: All events include Blueprint information
- Type Safety: Events are strongly typed with TypeScript
- Lifecycle Management: Automatic subscription cleanup
When to Use EventBus
✅ DO use EventBus for:
- Cross-module communication
- Broadcasting state changes to multiple listeners
- Audit logging and activity tracking
- Notification triggers
- Workflow orchestration
❌ DON'T use EventBus for:
- Simple parent-child component communication (use @Output)
- Direct service-to-service calls within same module
- Synchronous data fetching
- Request-response patterns (use Services)
Event Structure
Base Event Interface
interface BlueprintEvent<T = any> { type: string; // Format: [module].[action] blueprintId: string; timestamp: Date; actor: string; // User ID who triggered event data: T; metadata?: Record<string, any>; }
Event Naming Convention
[module].[action]
Examples:
task.createdtask.updatedtask.deletedtask.assignedmember.addedmember.removedfile.uploadedblueprint.archived
Publishing Events
Basic Event Publishing
import { inject } from '@angular/core'; import { BlueprintEventBus } from '@core/services/blueprint-event-bus.service'; @Injectable({ providedIn: 'root' }) export class TaskService { private eventBus = inject(BlueprintEventBus); private taskRepository = inject(TaskRepository); async createTask(blueprintId: string, task: CreateTaskDto): Promise<Task> { // 1. Execute business logic const created = await this.taskRepository.create(blueprintId, task); // 2. Publish domain event this.eventBus.publish({ type: 'task.created', blueprintId, timestamp: new Date(), actor: this.getCurrentUserId(), data: created }); return created; } async updateTask(taskId: string, updates: Partial<Task>): Promise<Task> { const task = await this.taskRepository.findById(taskId); if (!task) throw new Error('Task not found'); const updated = await this.taskRepository.update(taskId, updates); // Publish update event with before/after data this.eventBus.publish({ type: 'task.updated', blueprintId: task.blueprintId, timestamp: new Date(), actor: this.getCurrentUserId(), data: updated, metadata: { before: task, changes: updates } }); return updated; } }
Typed Event Publishing
// Define event types interface TaskCreatedEvent extends BlueprintEvent<Task> { type: 'task.created'; } interface TaskAssignedEvent extends BlueprintEvent<{ task: Task; assignee: string; assigneeType: 'user' | 'team' | 'partner'; }> { type: 'task.assigned'; } // Publish with type safety this.eventBus.publish<TaskCreatedEvent>({ type: 'task.created', blueprintId: created.blueprintId, timestamp: new Date(), actor: this.getCurrentUserId(), data: created });
Subscribing to Events
Basic Subscription
import { Component, inject, signal, DestroyRef } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { BlueprintEventBus } from '@core/services/blueprint-event-bus.service'; @Component({ selector: 'app-task-list', template: `...` }) export class TaskListComponent { private eventBus = inject(BlueprintEventBus); private destroyRef = inject(DestroyRef); tasks = signal<Task[]>([]); ngOnInit(): void { // Subscribe to task.created events this.eventBus.subscribe('task.created') .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(event => { console.log('New task created:', event.data); this.tasks.update(tasks => [...tasks, event.data]); }); } }
Filtered Subscription (Blueprint-specific)
import { filter } from 'rxjs/operators'; @Component({ selector: 'app-blueprint-tasks', template: `...` }) export class BlueprintTasksComponent { private eventBus = inject(BlueprintEventBus); private destroyRef = inject(DestroyRef); blueprintId = input.required<string>(); tasks = signal<Task[]>([]); ngOnInit(): void { // Only listen to events in current Blueprint this.eventBus.subscribe('task.created') .pipe( filter(event => event.blueprintId === this.blueprintId()), takeUntilDestroyed(this.destroyRef) ) .subscribe(event => { this.tasks.update(tasks => [...tasks, event.data]); }); // Listen to updates this.eventBus.subscribe('task.updated') .pipe( filter(event => event.blueprintId === this.blueprintId()), takeUntilDestroyed(this.destroyRef) ) .subscribe(event => { this.tasks.update(tasks => tasks.map(t => t.id === event.data.id ? event.data : t) ); }); // Listen to deletions this.eventBus.subscribe('task.deleted') .pipe( filter(event => event.blueprintId === this.blueprintId()), takeUntilDestroyed(this.destroyRef) ) .subscribe(event => { this.tasks.update(tasks => tasks.filter(t => t.id !== event.data.id) ); }); } }
Multiple Event Types
import { merge } from 'rxjs'; ngOnInit(): void { // Listen to multiple event types merge( this.eventBus.subscribe('task.created'), this.eventBus.subscribe('task.updated'), this.eventBus.subscribe('task.deleted') ) .pipe( filter(event => event.blueprintId === this.blueprintId()), takeUntilDestroyed(this.destroyRef) ) .subscribe(event => { console.log('Task event:', event.type, event.data); this.refreshTasks(); }); }
Common Use Cases
1. Audit Logging
@Injectable({ providedIn: 'root' }) export class AuditLogService { private eventBus = inject(BlueprintEventBus); private auditLogRepository = inject(AuditLogRepository); constructor() { // Listen to ALL events for audit trail this.eventBus.subscribeAll() .pipe(takeUntilDestroyed()) .subscribe(event => { this.logEvent(event); }); } private async logEvent(event: BlueprintEvent): Promise<void> { await this.auditLogRepository.create({ eventType: event.type, blueprintId: event.blueprintId, actor: event.actor, timestamp: event.timestamp, data: event.data, metadata: event.metadata }); } }
2. Notification System
@Injectable({ providedIn: 'root' }) export class NotificationService { private eventBus = inject(BlueprintEventBus); private notificationRepository = inject(NotificationRepository); constructor() { // Listen to events that trigger notifications this.setupNotificationListeners(); } private setupNotificationListeners(): void { // Task assigned → notify assignee this.eventBus.subscribe('task.assigned') .pipe(takeUntilDestroyed()) .subscribe(async event => { await this.notifyUser(event.data.assignee, { title: 'New Task Assigned', message: `You have been assigned: ${event.data.task.title}`, blueprintId: event.blueprintId }); }); // Member added → notify member this.eventBus.subscribe('member.added') .pipe(takeUntilDestroyed()) .subscribe(async event => { await this.notifyUser(event.data.userId, { title: 'Added to Blueprint', message: `You have been added to ${event.data.blueprintName}`, blueprintId: event.blueprintId }); }); } }
3. Real-time UI Updates
@Component({ selector: 'app-activity-feed', template: ` <div class="activity-feed"> @for (activity of activities(); track activity.id) { <div class="activity-item"> <span class="timestamp">{{ activity.timestamp | date }}</span> <span class="message">{{ activity.message }}</span> </div> } </div> ` }) export class ActivityFeedComponent { private eventBus = inject(BlueprintEventBus); private destroyRef = inject(DestroyRef); blueprintId = input.required<string>(); activities = signal<Activity[]>([]); ngOnInit(): void { // Listen to all events in Blueprint this.eventBus.subscribeAll() .pipe( filter(event => event.blueprintId === this.blueprintId()), takeUntilDestroyed(this.destroyRef) ) .subscribe(event => { this.addActivity({ id: crypto.randomUUID(), type: event.type, message: this.formatEventMessage(event), timestamp: event.timestamp }); }); } private formatEventMessage(event: BlueprintEvent): string { switch (event.type) { case 'task.created': return `Task "${event.data.title}" was created`; case 'task.assigned': return `Task assigned to ${event.data.assignee}`; case 'member.added': return `${event.data.userName} joined the Blueprint`; default: return `Event: ${event.type}`; } } private addActivity(activity: Activity): void { this.activities.update(activities => [activity, ...activities].slice(0, 50)); } }
4. Workflow Orchestration
@Injectable({ providedIn: 'root' }) export class TaskWorkflowService { private eventBus = inject(BlueprintEventBus); private taskRepository = inject(TaskRepository); private notificationService = inject(NotificationService); constructor() { this.setupWorkflows(); } private setupWorkflows(): void { // When task is completed → trigger follow-up actions this.eventBus.subscribe('task.completed') .pipe(takeUntilDestroyed()) .subscribe(async event => { const task = event.data; // 1. Check if task has dependencies const dependentTasks = await this.taskRepository .findDependentTasks(task.id); // 2. Update dependent tasks for (const depTask of dependentTasks) { await this.taskRepository.update(depTask.id, { status: 'ready' }); } // 3. Notify stakeholders await this.notificationService.notifyTaskCompletion(task); }); } }
Event Bus Service Implementation
Reference implementation:
import { Injectable } from '@angular/core'; import { Subject, Observable } from 'rxjs'; import { filter } from 'rxjs/operators'; export interface BlueprintEvent<T = any> { type: string; blueprintId: string; timestamp: Date; actor: string; data: T; metadata?: Record<string, any>; } @Injectable({ providedIn: 'root' }) export class BlueprintEventBus { private eventStream = new Subject<BlueprintEvent>(); /** * Publish event to all subscribers */ publish<T = any>(event: BlueprintEvent<T>): void { this.eventStream.next(event); } /** * Subscribe to specific event type */ subscribe<T = any>(eventType: string): Observable<BlueprintEvent<T>> { return this.eventStream.asObservable().pipe( filter(event => event.type === eventType) ); } /** * Subscribe to all events */ subscribeAll(): Observable<BlueprintEvent> { return this.eventStream.asObservable(); } /** * Subscribe to events in specific Blueprint */ subscribeToBlueprintEvents(blueprintId: string): Observable<BlueprintEvent> { return this.eventStream.asObservable().pipe( filter(event => event.blueprintId === blueprintId) ); } }
Testing EventBus Integration
describe('EventBus Integration', () => { let eventBus: BlueprintEventBus; let taskService: TaskService; beforeEach(() => { eventBus = TestBed.inject(BlueprintEventBus); taskService = TestBed.inject(TaskService); }); it('should publish event when task is created', (done) => { // Subscribe to event eventBus.subscribe('task.created').subscribe(event => { expect(event.type).toBe('task.created'); expect(event.data.title).toBe('Test Task'); done(); }); // Create task (triggers event) taskService.createTask('blueprint1', { title: 'Test Task' }); }); it('should filter events by Blueprint', (done) => { let receivedEvents = 0; eventBus.subscribeAll() .pipe(filter(event => event.blueprintId === 'blueprint1')) .subscribe(() => { receivedEvents++; }); // Publish events to different Blueprints eventBus.publish({ type: 'task.created', blueprintId: 'blueprint1', timestamp: new Date(), actor: 'user1', data: {} }); eventBus.publish({ type: 'task.created', blueprintId: 'blueprint2', timestamp: new Date(), actor: 'user1', data: {} }); setTimeout(() => { expect(receivedEvents).toBe(1); done(); }, 100); }); });
Checklist
When integrating EventBus:
- Events follow naming convention ([module].[action])
- Events include blueprintId
- Events include timestamp and actor
- Subscriptions use takeUntilDestroyed()
- Filter events by Blueprint when needed
- Use typed events for type safety
- Publish events AFTER successful operations
- Don't use EventBus for request-response
- Test event publishing and subscription
- Document event types and data structures
References
- Architecture Guide
- Blueprint Integration
- [Global Event Bus Documentation](docs/⭐️/Global Event Bus.md)