Documentation Index
Fetch the complete documentation index at: https://docs.deploystack.io/llms.txt
Use this file to discover all available pages before exploring further.
The DeployStack frontend implements a global event bus system using the mitt library to enable efficient cross-component communication. This system provides immediate updates across components without requiring direct parent-child relationships or complex state management.
Overview
The event bus solves common frontend challenges such as:
- Cross-component communication between unrelated components
- Immediate UI updates when data changes in different parts of the application
- Cache invalidation and data synchronization
- Decoupled architecture for better maintainability
Architecture
Event Bus Setup
The event bus is configured globally in main.ts using Vue 3’s provide/inject pattern:
// main.ts
import mitt from 'mitt'
import type { EventBusEvents } from './composables/useEventBus'
// Create typed event bus
const emitter = mitt<EventBusEvents>()
// Provide globally
app.provide('emitter', emitter)
Type Safety
All events are strictly typed using TypeScript interfaces:
// src/composables/useEventBus.ts
export type EventBusEvents = {
'teams-updated': void
'team-created': void
'team-deleted': void
'user-profile-updated': void
'mcp-server-deployed': { serverId: string; status: string }
'notification-show': { message: string; type: 'success' | 'error' | 'warning' }
'storage-changed': { key: string; oldValue: any; newValue: any }
}
Storage Integration
The event bus includes built-in storage capabilities for persistent state management. When you use storage methods, they automatically emit events for reactive updates.
Storage Events
The storage system emits storage-changed events whenever data is modified:
// Automatically emitted when using storage methods
eventBus.setState('selected_team_id', 'team-123')
// Emits: { key: 'selected_team_id', oldValue: null, newValue: 'team-123' }
// Listen for storage changes
eventBus.on('storage-changed', (data) => {
console.log(`Storage key "${data.key}" changed from ${data.oldValue} to ${data.newValue}`)
})
📖 For detailed storage usage, see Frontend Storage System
Usage
Basic Implementation
1. Using the Composable
// In any component
import { useEventBus } from '@/composables/useEventBus'
export default {
setup() {
const eventBus = useEventBus()
// Emit events
eventBus.emit('teams-updated')
// Listen to events
eventBus.on('teams-updated', () => {
console.log('Teams were updated!')
})
return {}
}
}
2. Component Lifecycle Management
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { useEventBus } from '@/composables/useEventBus'
const eventBus = useEventBus()
// Event handler function
const handleTeamsUpdate = () => {
console.log('Teams updated, refreshing data...')
fetchTeams(true) // Force refresh
}
onMounted(() => {
// Register event listeners
eventBus.on('teams-updated', handleTeamsUpdate)
})
onUnmounted(() => {
// Clean up event listeners to prevent memory leaks
eventBus.off('teams-updated', handleTeamsUpdate)
})
</script>
Real-World Examples
Example 1: Team Management System
This example shows how the sidebar automatically updates when teams are created from the teams page.
Emitting Events (Team Creation)
<!-- AddTeamModal.vue -->
<script setup lang="ts">
import { useEventBus } from '@/composables/useEventBus'
import { TeamService } from '@/services/teamService'
const eventBus = useEventBus()
const handleSubmit = async () => {
try {
await TeamService.createTeam(formData.value)
// Emit global event for immediate UI updates
eventBus.emit('teams-updated')
// Close modal and emit local success
isOpen.value = false
emit('teamCreated')
} catch (error) {
console.error('Error creating team:', error)
}
}
</script>
<!-- AppSidebar.vue -->
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useEventBus } from '@/composables/useEventBus'
import { TeamService } from '@/services/teamService'
const eventBus = useEventBus()
const teams = ref([])
const fetchTeams = async (forceRefresh = false) => {
try {
const userTeams = await TeamService.getUserTeams(forceRefresh)
teams.value = userTeams
} catch (error) {
console.error('Error fetching teams:', error)
}
}
onMounted(() => {
fetchTeams()
// Listen for team updates from other components
eventBus.on('teams-updated', () => {
console.log('Teams updated event received, refreshing teams...')
fetchTeams(true) // Force refresh to get latest data
})
})
onUnmounted(() => {
// Clean up event listeners
eventBus.off('teams-updated')
})
</script>
Example 2: Notification System
<!-- NotificationManager.vue -->
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useEventBus } from '@/composables/useEventBus'
const eventBus = useEventBus()
const notifications = ref([])
const showNotification = (data: { message: string; type: string }) => {
const notification = {
id: Date.now(),
message: data.message,
type: data.type,
timestamp: new Date()
}
notifications.value.push(notification)
// Auto-remove after 5 seconds
setTimeout(() => {
removeNotification(notification.id)
}, 5000)
}
const removeNotification = (id: number) => {
const index = notifications.value.findIndex(n => n.id === id)
if (index > -1) {
notifications.value.splice(index, 1)
}
}
onMounted(() => {
eventBus.on('notification-show', showNotification)
})
onUnmounted(() => {
eventBus.off('notification-show', showNotification)
})
</script>
Triggering Notifications
<!-- Any component -->
<script setup lang="ts">
import { useEventBus } from '@/composables/useEventBus'
const eventBus = useEventBus()
const handleSuccess = () => {
eventBus.emit('notification-show', {
message: 'Team created successfully!',
type: 'success'
})
}
const handleError = () => {
eventBus.emit('notification-show', {
message: 'Failed to create team',
type: 'error'
})
}
</script>
Example 3: MCP Server Deployment Status
<!-- McpServerCard.vue -->
<script setup lang="ts">
import { useEventBus } from '@/composables/useEventBus'
const eventBus = useEventBus()
const deployServer = async (serverId: string) => {
try {
const result = await McpServerService.deploy(serverId)
// Notify other components about deployment
eventBus.emit('mcp-server-deployed', {
serverId,
status: result.status
})
} catch (error) {
eventBus.emit('notification-show', {
message: 'Deployment failed',
type: 'error'
})
}
}
</script>
<!-- McpServerList.vue -->
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { useEventBus } from '@/composables/useEventBus'
const eventBus = useEventBus()
const handleServerDeployment = (data: { serverId: string; status: string }) => {
// Update server status in the list
const server = servers.value.find(s => s.id === data.serverId)
if (server) {
server.status = data.status
server.lastDeployed = new Date()
}
}
onMounted(() => {
eventBus.on('mcp-server-deployed', handleServerDeployment)
})
onUnmounted(() => {
eventBus.off('mcp-server-deployed', handleServerDeployment)
})
</script>
Event Naming Convention
The event bus follows a consistent naming pattern to ensure clarity and maintainability across the application.
Standard Pattern
This pattern makes events self-documenting and easy to understand at a glance.
Examples
// Team-related events
'teams-updated'
'team-created'
'team-deleted'
'team-selected'
// Credential events
'credentials-updated'
'credential-created'
'credential-deleted'
// MCP server events
'mcp-server-deployed'
'mcp-server-installed'
'mcp-installation-removed'
// System events
'notification-show'
'storage-changed'
'cache-cleared'
Naming Guidelines
- Use kebab-case: All event names should use lowercase with hyphens
- Feature first: Start with the feature or domain name
- Action second: End with the specific action or state change
- Be specific: Avoid generic names like ‘update’ or ‘change’ without context
- Past tense for completed actions: Use ‘created’, ‘updated’, ‘deleted’ for completed operations
- Present tense for commands: Use ‘show’, ‘hide’, ‘clear’ for immediate actions
Best Practices
1. Event Type Definition
Always define event types in the EventBusEvents interface for type safety:
export type EventBusEvents = {
// Simple events (no data)
'teams-updated': void
'cache-cleared': void
// Events with data
'user-updated': { userId: string; changes: Partial<User> }
'error-occurred': { message: string; code?: string }
'progress-update': { percentage: number; task: string }
}
2. Memory Management
Always clean up event listeners to prevent memory leaks:
<script setup lang="ts">
import { onUnmounted } from 'vue'
import { useEventBus } from '@/composables/useEventBus'
const eventBus = useEventBus()
// Store handler references for cleanup
const handleTeamsUpdate = () => { /* handler logic */ }
const handleUserUpdate = () => { /* handler logic */ }
onMounted(() => {
eventBus.on('teams-updated', handleTeamsUpdate)
eventBus.on('user-updated', handleUserUpdate)
})
onUnmounted(() => {
// Clean up all listeners
eventBus.off('teams-updated', handleTeamsUpdate)
eventBus.off('user-updated', handleUserUpdate)
})
</script>
3. Error Handling
Wrap event handlers in try-catch blocks:
const handleDataUpdate = (data: any) => {
try {
// Process the event data
updateLocalState(data)
} catch (error) {
console.error('Error handling data update event:', error)
// Optionally emit an error event
eventBus.emit('error-occurred', {
message: 'Failed to process data update',
code: 'EVENT_HANDLER_ERROR'
})
}
}
4. Debugging Events
Add logging for development:
const eventBus = useEventBus()
// Development logging
if (import.meta.env.DEV) {
eventBus.on('*', (type, data) => {
console.log(`[EventBus] ${type}:`, data)
})
}
Common Patterns
1. Data Synchronization
// Pattern: Emit after successful API operations
const createTeam = async (teamData: CreateTeamInput) => {
try {
const newTeam = await TeamService.createTeam(teamData)
eventBus.emit('teams-updated')
return newTeam
} catch (error) {
eventBus.emit('error-occurred', { message: 'Failed to create team' })
throw error
}
}
2. Cache Invalidation
// Pattern: Clear cache and refresh data
eventBus.on('teams-updated', () => {
TeamService.clearCache()
fetchTeams(true) // Force refresh
})
3. UI State Updates
// Pattern: Update UI state across components
eventBus.on('user-profile-updated', (userData) => {
// Update user avatar in header
userAvatar.value = userData.avatar
// Update user name in sidebar
userName.value = userData.name
})
1. Event Frequency
Be mindful of high-frequency events:
// Good: Debounce frequent events
import { debounce } from 'lodash-es'
const debouncedUpdate = debounce(() => {
eventBus.emit('search-updated')
}, 300)
// Bad: Emitting on every keystroke
onInput(() => {
eventBus.emit('search-updated') // Too frequent!
})
2. Event Data Size
Keep event payloads small:
// Good: Send only necessary data
eventBus.emit('user-updated', {
userId: user.id,
changes: { name: user.name }
})
// Avoid: Sending entire objects
eventBus.emit('user-updated', entireUserObject) // Too much data!
Testing
Unit Testing Events
// tests/components/TeamManager.test.ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import TeamManager from '@/components/TeamManager.vue'
describe('TeamManager', () => {
it('emits teams-updated event after creating team', async () => {
const mockEventBus = {
emit: vi.fn(),
on: vi.fn(),
off: vi.fn()
}
const wrapper = mount(TeamManager, {
global: {
provide: {
emitter: mockEventBus
}
}
})
await wrapper.vm.createTeam({ name: 'Test Team' })
expect(mockEventBus.emit).toHaveBeenCalledWith('teams-updated')
})
})
Migration Guide
From Direct Component Communication
Before:
<!-- Parent component -->
<ChildComponent @team-created="handleTeamCreated" />
<!-- Child component -->
emit('team-created', teamData)
After:
<!-- Parent component -->
<script setup>
const eventBus = useEventBus()
onMounted(() => {
eventBus.on('teams-updated', handleTeamCreated)
})
</script>
<!-- Child component -->
<script setup>
const eventBus = useEventBus()
const createTeam = async () => {
await TeamService.createTeam(data)
eventBus.emit('teams-updated')
}
</script>
Adding New Events
- Define the event type:
// src/composables/useEventBus.ts
export type EventBusEvents = {
// ... existing events
'new-feature-updated': { featureId: string; status: string }
}
- Emit the event:
eventBus.emit('new-feature-updated', {
featureId: 'feature-123',
status: 'active'
})
- Listen for the event:
eventBus.on('new-feature-updated', (data) => {
console.log(`Feature ${data.featureId} is now ${data.status}`)
})
Troubleshooting
Common Issues
- Events not firing: Check if the event name matches exactly
- Memory leaks: Ensure
eventBus.off() is called in onUnmounted()
- TypeScript errors: Verify event types are defined in
EventBusEvents
- Handler not called: Check if the listener was registered before the event was emitted
Debugging Tips
// Add global event logging
if (import.meta.env.DEV) {
const eventBus = useEventBus()
// Log all events
eventBus.on('*', (type, data) => {
console.group(`[EventBus] ${type}`)
console.log('Data:', data)
console.log('Timestamp:', new Date().toISOString())
console.groupEnd()
})
}
The global event bus system provides a powerful and type-safe way to handle cross-component communication in the DeployStack frontend, enabling immediate updates and better user experience.