Documentation
¶
Overview ¶
Package events provides a typed in-app publish/subscribe fan-out bus with component-lifecycle-tied subscriptions for the GoWebComponents framework.
Every topic is a plain string key. Subscribers are typed via generics; a subscriber registered for type T on topic "foo" only receives publications of type T on that same topic — mismatched types are silently skipped.
The package works without a running render loop (unit-testable on native Go) and compiles for both native and GOOS=js GOARCH=wasm targets.
Package events — typed_publish.go adds a topic-type codec registry that lets an AI agent bridge (or any JSON source) publish events that reach typed subscribers registered with Subscribe[T], INCLUDING composite types.
Background: a plain json.Unmarshal into `any` yields Go primitives for JSON primitives (string, float64, bool, nil) but a map[string]any for JSON objects. So an untyped Publish[any] DOES reach a Subscribe[T] handler when T is the matching primitive — the value's dynamic type satisfies the parseValue.(T) assertion in the deliver wrapper — but it NEVER reaches a Subscribe[SomeStruct] handler, because the value is a map[string]any, not the struct. The codec registry closes that gap: RegisterTopic[T] decodes the JSON into the concrete T and calls Publish[T], so subscribers of ANY type T — composite types included — receive the value.
Index ¶
- func Publish[T any](parseTopic string, parseValue T)
- func PublishJSON(parseTopic string, parseRaw json.RawMessage) error
- func RegisterTopic[T any](parseTopic string)
- func RegisteredTopicCodecs() []string
- func Subscribe[T any](parseTopic string, parseHandler func(T)) (parseUnsubscribe func())
- func SubscriberCount(parseTopic string) int
- type TopicHandle
- type TopicInfo
- type TopicOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Publish ¶
Publish delivers parseValue to every subscriber currently registered on parseTopic whose handler type matches T. Delivery order matches registration order. A panicking subscriber is contained; other subscribers still receive the value. Publish is goroutine-safe.
Example ¶
ExamplePublish shows the non-hook core: subscribe to a typed topic, publish to it, and unsubscribe when done. Inside a component prefer events.UseTopic, which ties the subscription to the component lifecycle.
package main
import (
"fmt"
"github.com/monstercameron/GoWebComponents/v4/events"
)
func main() {
parseUnsubscribe := events.Subscribe("greeting", func(parseValue string) {
fmt.Println("received:", parseValue)
})
defer parseUnsubscribe()
events.Publish("greeting", "hello")
events.Publish("greeting", "world")
}
Output: received: hello received: world
func PublishJSON ¶
func PublishJSON(parseTopic string, parseRaw json.RawMessage) error
PublishJSON decodes parseRaw and publishes the result on parseTopic.
If a codec has been registered for the topic via RegisterTopic[T], the JSON is unmarshalled into T and Publish[T] is called — this reaches every Subscribe[T] handler on that topic.
If NO codec is registered, the JSON is decoded into a generic any value (numbers become float64, objects become map[string]any, etc.) and Publish[any] is called. This reaches any-typed subscribers AND concretely-typed subscribers whose T matches the decoded primitive (Subscribe[string]/[float64]/[bool] receive a JSON string/number/bool), but does NOT reach composite-typed subscribers — a Subscribe[SomeStruct] sees a map[string]any, not its struct, so register a codec for those topics.
An error is returned when the registered codec's json.Unmarshal step fails (wrong JSON shape for T). JSON decode failures in the untyped fallback path are also returned. A missing topic codec is not an error.
func RegisterTopic ¶
RegisterTopic registers a JSON codec for parseTopic so that PublishJSON can decode an incoming JSON payload into the concrete Go type T and deliver it to every Subscribe[T] handler on that topic.
Applications call RegisterTopic once per typed topic they want an agent bridge (or any JSON source) to drive. Re-registration replaces the previous codec for the topic.
Example:
events.RegisterTopic[string]("chat.message")
events.RegisterTopic[MyStruct]("app.state")
func RegisteredTopicCodecs ¶
func RegisteredTopicCodecs() []string
RegisteredTopicCodecs returns a sorted list of topic strings that have a registered codec. Agent bridges and devtools use this to discover which topics accept a typed JSON publish.
func Subscribe ¶
Subscribe registers parseHandler to receive every future value published on parseTopic whose type matches T. It returns an unsubscribe function; calling it removes the handler and, when the last subscriber leaves, clears the internal registry entry for that topic.
Subscribe is goroutine-safe. The handler is invoked synchronously from Publish; a panicking handler is contained so it cannot stop delivery to sibling subscribers.
func SubscriberCount ¶
SubscriberCount returns the number of handlers currently registered on a topic, across all element types. It is the exported counterpart of the package-internal test helper.
Types ¶
type TopicHandle ¶
type TopicHandle[T any] struct { // contains filtered or unexported fields }
TopicHandle is returned by UseTopic and lets the caller publish values on the topic it was created for.
func UseTopic ¶
func UseTopic[T any](parseTopic string, parseHandler func(T), parseOptions ...TopicOption) TopicHandle[T]
UseTopic wires a typed pub/sub subscription to a component's lifetime via UseEffect. When parseHandler is non-nil the subscription is established during the effect phase and automatically removed when the component unmounts or when parseTopic changes. Pass nil for a publish-only component. Zero or more TopicOption values fine-tune subscription behaviour.
UseTopic returns a TopicHandle whose Publish method sends values on the topic. Because UseEffect is a no-op on native (non-wasm) builds, UseTopic's subscription side is also a no-op on native; use the core Subscribe/Publish functions directly in unit tests.
func (TopicHandle[T]) Publish ¶
func (parseH TopicHandle[T]) Publish(parseValue T)
Publish delivers parseValue to all subscribers currently registered on the topic this handle was created for.
func (TopicHandle[T]) Topic ¶
func (parseH TopicHandle[T]) Topic() string
Topic returns the topic name this handle publishes to, for logging and comparison.
type TopicInfo ¶
type TopicInfo struct {
// Topic is the topic string.
Topic string `json:"topic"`
// Subscribers is the number of handlers currently registered on the topic.
// Note this counts handlers of every element type T; a Publish[T] only
// delivers to handlers whose registered T matches, so a non-zero count is
// an upper bound on who will actually receive a given typed publish.
Subscribers int `json:"subscribers"`
}
TopicInfo describes one live pub/sub topic for agent/devtools introspection.
func Topics ¶
func Topics() []TopicInfo
Topics returns every topic the registry currently knows about, sorted by topic string. A topic appears once it has been published to or subscribed on at least once. This is the discovery surface an agent bridge uses to learn the in-app event vocabulary instead of guessing topic names.
type TopicOption ¶
type TopicOption struct {
// contains filtered or unexported fields
}
TopicOption is a functional option accepted by UseTopic.
func WithReplayLast ¶
func WithReplayLast() TopicOption
WithReplayLast returns a TopicOption that causes a new subscriber to immediately receive the most recently published value on its topic, if any value has been published since the program started.