Documentation
¶
Overview ¶
Package event provides a typed in-process event bus: topics fix payload types, listeners match by name or pattern, and dispatch is synchronous.
A Topic names one kind of event and fixes its payload type. Bus.Listen registers a typed listener and Bus.Dispatch invokes the listeners of a topic in registration order, stopping at the first error; exact listeners run before Bus.ListenRegex listeners. Event names are dot-separated lowercase tokens such as "user.created"; Bus.Listen and Bus.Dispatch return ErrInvalidName for any other name.
var UserCreated = event.NewTopic[User]("user.created")
bus := event.New()
unsubscribe, err := bus.Listen(UserCreated, func(ctx context.Context, user User) error {
return sendWelcome(ctx, user)
})
The application owns one bus (arkoren.dev/core/application.Application.Events). Arkoren components take it with their builder's Events setting. They publish topics with Bus.Emit and publish nothing without it. Emit logs listener errors and panics to Builder.Logger instead of returning them, so a listener never changes the outcome of the operation it observes. Listeners run synchronously on the caller's goroutine with its context: keep them short, and hand slow work to a queue. Free functions such as auth.SessionIdentity.Login publish nothing; their caller emits its own events.
A signal is a topic without data: NewSignal declares it and Bus.Signal publishes it. Event signals are unrelated to workflow signals, which are messages sent to a running workflow.
Payloads never carry secrets, cache or storage contents, tokens, session IDs, request bodies, or full URLs. Errors travel as the values the caller of the operation receives.
Construction ¶
New applies callbacks directly once, synchronously in order on one fresh Builder. Settings return the same draft; publication copies the root, not the logger. Retained drafts cannot change a bus. Omitted callbacks and nil/empty expanded slices make no edits; supplied nil entries and callback panics propagate. A nil logger resolves slog.Default when a failure is logged.
events := event.New(func(events *event.Builder) {
events.Logger(slog.Default())
})
Index ¶
- Variables
- type Builder
- type BuilderFunc
- type Bus
- func (bus *Bus) Dispatch[T any](ctx context.Context, topic Topic[T], payload T) error
- func (bus *Bus) Emit[T any](ctx context.Context, topic Topic[T], payload T)
- func (bus *Bus) Listen[T any](topic Topic[T], listener func(context.Context, T) error) (Unsubscribe, error)
- func (bus *Bus) ListenEvent(eventName string, listener Listener) (Unsubscribe, error)
- func (bus *Bus) ListenRegex(pattern *regexp.Regexp, listener RegexListener) (Unsubscribe, error)
- func (bus *Bus) Signal(ctx context.Context, signal Topic[Void])
- type Listener
- type RegexListener
- type Topic
- type Unsubscribe
- type Void
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidName reports an event name that [Bus.ListenEvent] rejects. ErrInvalidName = errors.New("invalid event name") // ErrNilFunc reports a registration without a function. ErrNilFunc = errors.New("nil function") )
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder configures one event bus during New. Settings are copied before New returns; retaining this draft cannot reconfigure the published bus. Loggers remain shared dependencies, not cloned values.
events := event.New(func(events *event.Builder) {
events.Logger(slog.Default())
})
func (*Builder) Logger ¶
Logger selects the logger for errors and panics swallowed by Bus.Emit. Nil restores slog.Default resolution when an error is logged. It returns the same draft, and does not invoke or copy the logger.
events := event.New(func(events *event.Builder) {
events.Logger(slog.Default())
})
type Bus ¶
type Bus struct {
// contains filtered or unexported fields
}
Bus synchronously dispatches in-process events by name.
func New ¶
func New(configure ...BuilderFunc) *Bus
New creates an empty bus. Callbacks run directly once, synchronously in order on one fresh draft. A supplied nil callback or callback panic propagates; omitted callbacks and expanded nil/empty slices make no edits. Publication copies settings, but the logger remains shared.
events := event.New(func(events *event.Builder) {
events.Logger(slog.Default())
})
func (*Bus) Dispatch ¶
Dispatch invokes the listeners of topic with payload. It stops at the first listener error. Exact listeners run before matching regular expression listeners. A listener panic is not recovered; use Bus.Emit when a listener must never affect the caller. It returns ErrInvalidName without invoking any listener when the topic name is not a valid event name.
func (*Bus) Emit ¶
Emit dispatches payload like Bus.Dispatch for code that reports what happened, where a listener must never change the outcome: a listener error or panic is logged with the event name instead of returned, and the remaining listeners are skipped. Builder.Logger selects the logger; nil resolves slog.Default when the failure is logged. A nil bus does nothing, so a component built without an events option pays one comparison.
Example:
events := event.New(func(events *event.Builder) {
events.Logger(logger)
})
events.Emit(ctx, event.NewTopic[int]("orders.created"), 42)
func (*Bus) Listen ¶
func (bus *Bus) Listen[T any](topic Topic[T], listener func(context.Context, T) error) (Unsubscribe, error)
Listen registers a typed listener for topic and returns a function that removes it. Events on the same name whose payload is not a T are skipped. It returns ErrInvalidName when the topic name is not a valid event name and ErrNilFunc when listener is nil.
Example:
unsubscribe, err := bus.Listen(UserCreated, func(ctx context.Context, user User) error {
return sendWelcome(ctx, user)
})
func (*Bus) ListenEvent ¶
func (bus *Bus) ListenEvent(eventName string, listener Listener) (Unsubscribe, error)
ListenEvent registers listener for eventName and returns a function that removes it. A valid name is one or more dot-separated tokens of 1-64 lowercase letters, digits, or hyphens with no hyphen at either end (at most 255 bytes in total). It returns ErrInvalidName for any other name and ErrNilFunc when listener is nil.
bus := event.New()
unsubscribe, err := bus.ListenEvent("orders.created", func(ctx context.Context, payload any) error {
return nil
})
if err != nil {
panic(err)
}
unsubscribe()
func (*Bus) ListenRegex ¶
func (bus *Bus) ListenRegex(pattern *regexp.Regexp, listener RegexListener) (Unsubscribe, error)
ListenRegex registers listener for event names matched by pattern and returns a function that removes it. The pattern is not anchored: it matches any name that contains a match, so `storage` also matches "app.storage-check"; write `^storage\.` to match a prefix. It returns ErrNilFunc when pattern or listener is nil.
type RegexListener ¶
RegexListener handles an in-process event matched by a regular expression.
type Topic ¶
type Topic[T any] struct { // contains filtered or unexported fields }
Topic names one kind of event and fixes the payload type T carried by it. Topics with the same name share listeners, so two topics must not use one name with different payload types. The name is validated when the topic is listened to or dispatched; see Bus.ListenEvent for the rules.
func NewSignal ¶
NewSignal returns the topic called eventName that carries no data. Publish it with Bus.Signal. It is unrelated to workflow signals, which are messages sent to a running workflow.
var Started = event.NewSignal("app.started")
func NewTopic ¶
NewTopic returns the topic called eventName that carries payloads of type T. Construction does not validate the name; registration and dispatch report invalid names. Topics sharing a name must also share their payload type.
topic := event.NewTopic[int]("orders.created")
fmt.Println(topic.Name())
type Unsubscribe ¶
type Unsubscribe = func()
Unsubscribe removes a listener registration. Calling it more than once is safe.
type Void ¶
type Void struct{}
Void is the payload type of a signal: a topic that carries no data.
var Empty Void
Empty is the Void payload, for Bus.Dispatch on a signal.
err := bus.Dispatch(ctx, Started, event.Empty)