Help us shape the future of Go! Take the 2026 Go Developer Survey

event

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 10 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

func (events *Builder) Logger(logger *slog.Logger) *Builder

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 BuilderFunc

type BuilderFunc = func(*Builder)

BuilderFunc configures a Builder.

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

func (bus *Bus) Dispatch[T any](ctx context.Context, topic Topic[T], payload T) error

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

func (bus *Bus) Emit[T any](ctx context.Context, topic Topic[T], payload T)

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.

func (*Bus) Signal

func (bus *Bus) Signal(ctx context.Context, signal Topic[Void])

Signal publishes signal like Bus.Emit with the Empty payload.

Example:

app.Events().Signal(ctx, application.Started)

type Listener

type Listener = func(context.Context, any) error

Listener handles an in-process event.

type RegexListener

type RegexListener = func(context.Context, string, any) error

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

func NewSignal(eventName string) Topic[Void]

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

func NewTopic[T any](eventName string) Topic[T]

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())

func (Topic[T]) Name

func (topic Topic[T]) Name() string

Name returns the event name of the topic.

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)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL