Documentation
¶
Overview ¶
Package hub is a package-level singleton event hub: it broadcasts graph "hit" (search) and "change" (incremental update) events to connected WebSocket clients so the UI can render a live "graph in use" effect.
Ported from an earlier multi-tenant memory hub, trimmed for a single-process local tool: xray-memory has no auth, no tenant/grant filtering, and no multi-replica gossip, so those layers (and the userHash-scoped subscribe variant they required) are left out entirely — every subscriber sees every frame.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ClientCount ¶
func ClientCount() int
ClientCount returns the number of currently-connected WebSocket subscribers, so a caller can skip sampling/publishing work when nobody is listening.
func PublishChange ¶
func PublishChange(evt ChangeEvent)
PublishChange broadcasts a graph-update event to all connected subscribers.
func PublishHit ¶
func PublishHit(evt HitEvent)
PublishHit broadcasts a hit event to all connected subscribers and increments each QN's hit counter. A cheap no-op when there are no subscribers AND evt carries no QNs (the common case for HitCount-only callers is still cheap: the map lookups below are O(len(QNs))).
func ResetForTest ¶
func ResetForTest()
ResetForTest restores the hub to its zero state (no clients, no hit counts) so tests don't leak state across each other through this package-level singleton.
func ServeWS ¶
func ServeWS(logger zerolog.Logger) http.HandlerFunc
ServeWS upgrades the connection and streams hub Frame envelopes (`{"type":"hit"|"change","data":...}`), one per WebSocket text message, until the client disconnects. Server -> client push only: the client is never expected to send anything, so the read pump below exists solely to notice a disconnect and answer pong frames.
func Subscribe ¶
func Subscribe() (<-chan Frame, func())
Subscribe registers a new fan-out subscriber and returns a receive-only channel of Frame envelopes plus an unsubscribe func the caller must invoke exactly once (typically via defer) when the connection closes.
The channel is buffered at 8 and every send to it is non-blocking (see broadcast) — a slow reader drops frames rather than stalling a publisher. This is a live "in use" indicator, not a durable event log: a dropped frame just means one search's highlight animation is missed, not a correctness problem, and a publisher (every search request) must never be made to wait on a WebSocket write.
Types ¶
type ChangeEvent ¶
type ChangeEvent struct {
Op string `json:"op"` // "update"
Paths []string `json:"paths,omitempty"`
}
ChangeEvent is broadcast whenever the graph is updated in place, carrying the paths that were actually re-indexed — never the skipped (CGO-free build, minified) or unowned ones, since those describe no real graph change a client needs to react to.
type Frame ¶
type Frame struct {
Type FrameType `json:"type"`
Data json.RawMessage `json:"data"`
}
Frame is the one wire envelope every subscriber receives over GET /ws: `{"type": "hit"|"change", "data": <payload>}`. json.RawMessage lets broadcast marshal the payload once and every subscriber decode it independently, without a second round of re-marshaling per client.
type FrameType ¶
type FrameType string
FrameType is the WebSocket envelope's explicit discriminator (see Frame). An explicit field beats shape-sniffing the payload: HitEvent and ChangeEvent don't overlap today, but a client should never have to guess which one it received from field presence alone.
type HitEvent ¶
type HitEvent struct {
QNs []string `json:"qns"`
Query string `json:"query,omitempty"`
// Kinds is the per-hit node kind, parallel to QNs (same order, same
// length) — one of "Func" "Test" "Class" "Type" "File" "Doc" "Section"
// "Manifest". Added so a client can resolve a hit's category even when
// that node isn't currently drawn on a bounded graph view. Additive
// (omitempty): a caller that only has QNs still compiles and behaves as
// before with Kinds left nil. A client that finds Kinds shorter than QNs
// must fall back to QN-only behavior rather than index out of range.
Kinds []string `json:"kinds,omitempty"`
}
HitEvent is broadcast whenever a search resolves (over any surface — MCP, HTTP /search, or a direct in-process call, see index.Index.Search's own comment for why that one call site is authoritative).