Event Architecture
Event Contract
Core Principles
-
Backend is the sole event producer
Go services emit events viaapp.Event.Emit(). Frontend never callsEvents.Emit()for domain topics. -
Frontend is a pure consumer
Components subscribe viaEvents.On()and react—no re-fetch RPC needed when the event payload is sufficient. -
Events emit after successful DB write
Never emit speculatively. Only emit after state has been persisted. -
Event constants declared in Go
All event names defined asconstinservices/events.go. TypeScript listeners use the same string literals.
Event Catalog
| Event | Emitted By | Payload Type | When |
|---|---|---|---|
app:log | All services | LogEntry | Every significant service action |
connection:created | ConnectionService.CreateConnection | ConnectionCreatedEvent | After successful DB insert |
connection:deleted | ConnectionService.DeleteConnection | ConnectionDeletedEvent | After successful DB delete |
connections-window:closed | App.CloseConnectionsWindow | bool | When connections window is hidden |
menu:logs-toggled | Native menu (macOS) | nil | User selects “Toggle Logs” menu item |
plugins:ready | PluginManager (async scan) | nil | Initial plugin scan completes |
app:log is a stream channel, not a state-change event—it does not follow the past-tense verb rule.
Event Definitions
app:log
Constant:services.EventAppLog = "app:log"Emitted By: All services (ConnectionService, PluginManager, etc.)
Purpose: Stream structured log entries to the frontend Payload (
services/events.go:42):
views/Home.vue:115):
connection:created
Constant:services.EventConnectionCreated = "connection:created"Emitted By:
ConnectionService.CreateConnection (connection.go:261)Purpose: Notify frontend that a new connection was persisted Payload (
services/events.go:49):
connection.go:261):
Connections.vue):
ListConnections() call after creating a connection.
connection:deleted
Constant:services.EventConnectionDeleted = "connection:deleted"Emitted By:
ConnectionService.DeleteConnection (connection.go:327)Purpose: Notify frontend that a connection was removed Payload (
services/events.go:54):
connection.go:327):
menu:logs-toggled
Constant:services.EventMenuLogsToggled = "menu:logs-toggled"Emitted By: Native application menu (macOS only,
services/menu.go)Purpose: Request frontend toggle the logs panel Payload:
nil
Frontend Handler (views/Home.vue:122):
plugins:ready
Constant:services.EventPluginsReady = "plugins:ready"Emitted By:
PluginManager (after async scan completes, pluginmgr.go:257)Purpose: Signal frontend that
ListPlugins() is now populated
Payload: nil
Emission (pluginmgr.go:249):
Plugins.vue):
Naming Conventions
Backend Event Names
Format:<domain>:<past-tense-verb>
| Rule | Correct | Wrong |
|---|---|---|
| Lowercase only | connection:created | Connection:Created |
| Colon separator | plugin:scanned | plugin.scanned |
| Domain is singular noun | connection:deleted | connections:deleted |
| Verb is past tense for state changes | connection:created | connection:create |
Declared as Go const | ✓ | Inline string literals |
app:log use present tense (they don’t represent state changes).
Declaration (services/events.go:12):
Log Message Naming Rules
Format:"<MethodName>: <lowercase description>"
| Rule | Correct | Wrong |
|---|---|---|
| Method name is PascalCase | "CreateConnection: ..." | "create_connection: ..." |
| Description starts lowercase | "CreateConnection: creating 'db1'" | "CreateConnection: Creating 'db1'" |
| No trailing period | "connection deleted" | "connection deleted." |
| Single-quoted identifiers | "creating 'my-db'" | "creating my-db" |
| KV context in parentheses | "(driver: mysql, id: abc)" | "[driver=mysql]" |
Log Levels
| Level | When | Examples |
|---|---|---|
info | Normal lifecycle: start, success, counts | "CreateConnection: creating 'db1'" |
warn | Recoverable: fallback triggered, optional resource missing | "OS keyring unavailable, falling back to SQLite" |
error | Non-recoverable: DB failure, credential loss, plugin crash | "CreateConnection: failed to store credential" |
services/events.go:34):
Vue Component Emits
Format:kebab-case
| Rule | Correct | Wrong |
|---|---|---|
| Kebab-case | "tab-closed" | "tabClosed" |
| Past tense for notifications | "connection-selected" | "select-connection" |
| Noun phrase for data delivery | "query-result" | "send-query-result" |
| Imperative only for parent requests | "toggle-logs" | — |
| Never use backend event names | ✓ | emit("connection:created") |
components/WorkspacePanel.vue):
Event Emission Helpers
Nil-Safe Pattern
All event emission helpers check ifapp is nil before emitting. This keeps services testable without Wails.
Implementation (services/events.go:60):
services/connection.go:183):
SetApp() being called.
Domain Event Helpers
ConnectionCreatedEvent (services/events.go:72):
services/events.go:80):
Frontend Event Patterns
Subscription Lifecycle
Pattern: Subscribe inonMounted, unsubscribe in onUnmounted
Reactive State Updates
Anti-Pattern (polling):- No stale data (backend is source of truth)
- No race conditions (event guarantees DB write completed)
- Faster UI updates (no round-trip latency)
Adding a New Event
Backend Checklist
-
Add constant in
services/events.gowith doc comment -
Add payload struct (if needed)
-
Add emission helper following nil-safe pattern
-
Call helper after DB write succeeds
-
Register event type in
main.go(for TypeScript bindings) - Update Event Catalog in this document
Frontend Checklist
-
Subscribe to event in relevant component
-
Unsubscribe on unmount
Event Flow Diagrams
Connection Creation Flow
Log Streaming Flow
Debugging Events
Backend Logging
All event emissions are logged atinfo level:
Frontend Console
Log all received events:'*' wildcard may not be supported in all Wails versions. Use specific event names instead.
Event Inspector (Future)
Consider adding a debug panel that lists:- All registered events
- Emission timestamps
- Payload inspection
Performance Considerations
Event Payload Size
Keep payloads minimal. For large datasets, prefer:Event Throttling
For high-frequency events (e.g., progress updates), throttle on frontend:Security Considerations
No Credentials in Events
Never emit credential data in events:Log Sanitization
Avoid logging sensitive data:Next Steps
- System Architecture Overview - High-level system design
- Core Services - Backend service API documentation
- Frontend Architecture - Vue 3 component structure