| name | wps-events |
| description | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components. |
WPS Events Guide
Overview
WPS (Wave PubSub) is Wave Terminal's publish-subscribe event system that enables different parts of the application to communicate asynchronously. The system uses a broker pattern to route events from publishers to subscribers based on event types and scopes.
Key Files
pkg/wps/wpstypes.go - Event type constants and data structures
pkg/wps/wps.go - Broker implementation and core logic
pkg/wcore/wcore.go - Example usage patterns
Event Structure
Events in WPS have the following structure:
type WaveEvent struct {
Event string `json:"event"`
Scopes []string `json:"scopes,omitempty"`
Sender string `json:"sender,omitempty"`
Persist int `json:"persist,omitempty"`
Data any `json:"data,omitempty"`
}
Adding a New Event Type
Step 1: Define the Event Constant
Add your event type constant to pkg/wps/wpstypes.go:
const (
Event_BlockClose = "blockclose"
Event_ConnChange = "connchange"
Event_YourNewEvent = "your:newevent"
)
Naming Convention:
- Use descriptive PascalCase for the constant name with
Event_ prefix
- Use lowercase with colons for the string value (e.g., "namespace:eventname")
- Group related events with the same namespace prefix
- Always add a
// type: <TypeName> comment; use // type: none if no data is sent
Step 2: Add to AllEvents
Add your new constant to the AllEvents slice in pkg/wps/wpstypes.go:
var AllEvents []string = []string{
Event_YourNewEvent,
}
Step 3: Register in WaveEventDataTypes (REQUIRED)
You must add an entry to WaveEventDataTypes in pkg/tsgen/tsgenevent.go. This drives TypeScript type generation for the event's data field:
var WaveEventDataTypes = map[string]reflect.Type{
wps.Event_YourNewEvent: reflect.TypeOf(YourEventData{}),
}
- Use
reflect.TypeOf(YourType{}) for value types
- Use
reflect.TypeOf((*YourType)(nil)) for pointer types
- Use
nil if no data is sent for the event
Step 4: Define Event Data Structure (Optional)
If your event carries structured data, define a type for it:
type YourEventData struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
Step 5: Expose Type to Frontend (If Needed)
If your event data type isn't already exposed via an RPC call, you need to add it to pkg/tsgen/tsgen.go so TypeScript types are generated:
var ExtraTypes = []any{
waveobj.ORef{},
uctypes.RateLimitInfo{},
YourEventData{},
}
Then run code generation:
task generate
This will update frontend/types/gotypes.d.ts with TypeScript definitions for your type, ensuring type safety in the frontend when handling these events.
Publishing Events
Basic Publishing
To publish an event, use the global broker:
import "github.com/wavetermdev/waveterm/pkg/wps"
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_YourNewEvent,
Data: yourData,
})
Publishing with Scopes
Scopes allow targeted event delivery. Subscribers can filter events by scope:
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_WaveObjUpdate,
Scopes: []string{oref.String()},
Data: updateData,
})
Publishing in a Goroutine
To avoid blocking the caller, publish events asynchronously:
go func() {
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_YourNewEvent,
Data: data,
})
}()
When to use goroutines:
- When publishing from performance-critical code paths
- When the event is informational and doesn't need immediate delivery
- When publishing from code that holds locks (to prevent deadlocks)
Event Persistence
Events can be persisted in memory for late subscribers:
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_YourNewEvent,
Persist: 100,
Data: data,
})
Complete Example: Rate Limit Updates
This example shows how rate limit information is published when AI chat responses include rate limit headers.
1. Define the Event Type
In pkg/wps/wpstypes.go:
const (
Event_WaveAIRateLimit = "waveai:ratelimit"
)
2. Publish the Event
In pkg/aiusechat/usechat.go:
import "github.com/wavetermdev/waveterm/pkg/wps"
func updateRateLimit(info *uctypes.RateLimitInfo) {
if info == nil {
return
}
rateLimitLock.Lock()
defer rateLimitLock.Unlock()
globalRateLimitInfo = info
go func() {
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_WaveAIRateLimit,
Data: info,
})
}()
}
3. Subscribe to the Event (Frontend)
In the frontend, subscribe to events via WebSocket:
const subscription = {
event: "waveai:ratelimit",
allscopes: true,
};
Subscribing to Events
From Go Code
wps.Broker.Subscribe(routeId, wps.SubscriptionRequest{
Event: wps.Event_YourNewEvent,
AllScopes: true,
})
wps.Broker.Subscribe(routeId, wps.SubscriptionRequest{
Event: wps.Event_WaveObjUpdate,
Scopes: []string{"workspace:123"},
})
wps.Broker.Unsubscribe(routeId, wps.Event_YourNewEvent)
Scope Matching
Scopes support wildcard matching:
* matches a single scope segment
** matches multiple scope segments
wps.Broker.Subscribe(routeId, wps.SubscriptionRequest{
Event: wps.Event_WaveObjUpdate,
Scopes: []string{"workspace:*"},
})
Best Practices
-
Use Namespaces: Prefix event names with a namespace (e.g., waveai:, workspace:, block:)
-
Don't Block: Use goroutines when publishing from performance-critical code or while holding locks
-
Type-Safe Data: Define struct types for event data rather than using maps
-
Scope Wisely: Use scopes to limit event delivery and reduce unnecessary processing
-
Document Events: Add comments explaining when events are fired and what data they carry
-
Consider Persistence: Use Persist for events that late subscribers might need (like status updates). This is normally not used. We normally do a live RPC call to get the current value and then subscribe for updates.
Common Event Patterns
Status Updates
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_ControllerStatus,
Scopes: []string{blockId},
Persist: 1,
Data: statusData,
})
Object Updates
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_WaveObjUpdate,
Scopes: []string{oref.String()},
Data: waveobj.WaveObjUpdate{
UpdateType: waveobj.UpdateType_Update,
OType: obj.GetOType(),
OID: waveobj.GetOID(obj),
Obj: obj,
},
})
Batch Updates
func (b *BrokerType) SendUpdateEvents(updates waveobj.UpdatesRtnType) {
for _, update := range updates {
b.Publish(WaveEvent{
Event: Event_WaveObjUpdate,
Scopes: []string{waveobj.MakeORef(update.OType, update.OID).String()},
Data: update,
})
}
}
Debugging
To debug event flow:
- Check broker subscription map:
wps.Broker.SubMap
- View persisted events:
wps.Broker.ReadEventHistory(eventType, scope, maxItems)
- Add logging in publish/subscribe methods
- Monitor WebSocket traffic in browser dev tools
Quick Reference
When adding a new event: