events

package
v0.231.0 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package events owns the in-process Server-Sent Events hub for the mock's GET /events endpoint. The hub fans out events posted via POST /admin0/events to every connected subscriber whose event_type filter matches, backed by a bounded ring buffer that lets dropped subscribers resume via Last-Event-ID / ?from / ?from_timestamp.

The package wraps github.com/tmaxmax/go-sse: sse.Server is the HTTP handler, sse.Joe is the in-memory pub/sub provider, and recordingReplayer (this package) adds the (id, timestamp) index needed to translate ?from_timestamp into an event ID for the library's Last-Event-ID-driven Replay path.

Index

Constants

View Source
const DefaultKeepAliveInterval = 15 * time.Second

DefaultKeepAliveInterval is the cadence at which a `:keep-alive` comment is broadcast to every connected subscriber. 15s matches what most SSE deployments use; the library doesn't auto-emit.

View Source
const DefaultReconnectHint = 3 * time.Second

DefaultReconnectHint is the value sent in the SSE `retry:` field on connect, telling clients how long to wait before reconnecting after a disconnect (Auth0's Events API sends one too). A value <= 0 omits the hint, so clients fall back to their built-in default.

Variables

This section is empty.

Functions

func SetKeepAliveIntervalForTest

func SetKeepAliveIntervalForTest(t interface{ Cleanup(func()) }, d time.Duration)

SetKeepAliveIntervalForTest changes the keep-alive cadence for the duration of a single test. Registers t.Cleanup to restore the original value, so tests can't accidentally bleed configuration between cases. Intended for use only from _test.go files.

The new cadence only affects Hub instances constructed AFTER the call: runKeepAlive captures the duration in its time.Ticker at hub startup, so changing it later doesn't retro-actively shorten an already-running ticker. Build the hub inside the test (or right after SetKeepAliveIntervalForTest) to apply the override.

Types

type Event

type Event struct {
	Type    string
	ID      string
	Payload json.RawMessage
}

Event is the wire shape the control plane pushes into the hub. Type is the CloudEvent discriminator (e.g. "user.created") and drives the SSE `event:` field. ID is the SSE `id:` field — the offset cursor a consumer resumes from; when empty the message is delivered live but never stored for replay (error control frames). Payload is the raw JSON body streamed in the SSE `data:` field — it MUST include the same `type` value (the OpenAPI schema requires it as the oneOf discriminator), but this type doesn't enforce that; the /admin0/events handler validates against the schema before calling Hub.Publish.

type Hub

type Hub struct {
	// contains filtered or unexported fields
}

Hub is the SSE fan-out the mock owns. One Hub per process; the HTTP handler at GET /events is hub.Handler(), and POST /admin0/events pushes via hub.Publish. Hub is safe for concurrent use; every underlying primitive (sse.Server, sse.Joe, recordingReplayer) is.

Lifecycle:

  • NewHub starts a keep-alive goroutine.
  • Reset drains current subscribers and rebuilds the underlying server + replay buffer, so /admin0/reset between tests is non-destructive to the hub itself.
  • Shutdown drains every subscriber permanently and stops the keep-alive goroutine; intended for process shutdown.

func NewHub

func NewHub(bufferSize int, now func() time.Time, opts ...HubOption) (*Hub, error)

NewHub constructs a Hub. BufferSize is the cap of the replay buffer (used for resume via Last-Event-ID / ?from / ?from_timestamp); values <= 0 disable replay entirely (sse.Joe accepts a nil Replayer); values of 1 are clamped to 2 because the library requires a count of at least 2. Now is the clock the replayer's timestamp index uses; nil falls back to time.Now. The caller should wire this to internal/clock.Clock.Now when a controllable clock is present so ?from_timestamp behaves deterministically in clock-controlled tests. Opts customise the hub — e.g. WithReconnectHint sets the SSE retry: value sent on connect.

func (*Hub) ActiveSubscribers added in v0.229.0

func (h *Hub) ActiveSubscribers() int

ActiveSubscribers reports how many subscribers are connected to GET /events right now. A subscriber leaves the active set only when the server's read loop notices its connection closed, so a reading taken immediately after a client disconnects may briefly lag.

func (*Hub) Handler

func (h *Hub) Handler() http.Handler

Handler returns the HTTP handler for GET /events. Wire it under bearer middleware at mount time.

The handler:

  1. Disables the http.Server WriteTimeout for this connection (SSE is long-lived; the server default would tear down healthy subscribers after the configured timeout).
  2. Promotes Auth0's ?from and ?from_timestamp query parameters to the SSE-spec Last-Event-ID header so the library's native replay path picks them up. ?from wins over ?from_timestamp. ?from_timestamp accepts RFC 3339; clients that send the timezone `+` unencoded (which URL-decodes to space) are tolerated by retrying with the space restored.
  3. Surfaces aged-out resume requests as 410 Gone (matching the OpenAPI declaration). Unparseable ?from_timestamp returns 400 with the standard mgmt error envelope.
  4. Pre-flushes the SSE response headers so http.Client.Do returns immediately rather than waiting for the first event.
  5. Tracks the request context in the active set so Reset / Shutdown can drain in-flight subscribers cleanly.
  6. Delegates to the underlying *sse.Server, which uses an OnSession callback to parse `?event_type=...` into the subscriber's topic list.

func (*Hub) Publish

func (h *Hub) Publish(evt Event) error

Publish broadcasts evt to every subscriber whose topic set intersects. A regular event is sent to broadcastTopic (reaches every filterless subscriber) and to evt.Type (reaches every filtered subscriber that listed this type). Error frames and offset-only progress markers are stream-wide control signals routed to the every-subscriber topic — see the branches below. Keep-alives use a separate topic and never go through this method.

The RLock is held across server.Publish so a concurrent Reset can't swap h.server underneath an in-flight publish and produce a spurious "provider is closed" error.

func (*Hub) Reset

func (h *Hub) Reset(ctx context.Context) error

Reset swaps in a fresh server + replay buffer (so any concurrent Publish atomically moves to the new instance), drains the subscribers that were attached to the old server, then shuts the old server down — all while the new server is already serving new subscribers and publishes. The swap-before-shutdown ordering is what closes the Publish/Reset race: a publisher that grabbed the RLock immediately before Reset's mu.Lock acquired sees the OLD server (and Reset's Lock blocks until the publish completes because Publish holds RLock across the call); every publisher after that sees the NEW server. The OLD server is then shut down with no concurrent publish in flight.

Intended for the /admin0/reset control-plane hook between tests. Idempotent under concurrent callers (serialised via lifecycleMu).

func (*Hub) Shutdown

func (h *Hub) Shutdown(ctx context.Context) error

Shutdown drains every subscriber, stops the keep-alive goroutine, and marks the hub closed permanently. Intended for process shutdown. Idempotent — extra calls are no-ops.

Uses the same swap-before-shutdown ordering as Reset to keep in-flight publishers race-free: the swap to a nil server happens atomically under mu.Lock, then the old server is shut down with no lock held.

func (*Hub) TotalSubscribers added in v0.229.0

func (h *Hub) TotalSubscribers() int

TotalSubscribers reports how many subscriptions have connected since the hub was created or last Reset. It increments on every connect and never decrements within a window; Reset zeroes it.

type HubOption added in v0.230.0

type HubOption func(*Hub)

HubOption customises a Hub at construction.

func WithReconnectHint added in v0.230.0

func WithReconnectHint(d time.Duration) HubOption

WithReconnectHint sets the SSE `retry:` reconnect-delay hint sent on connect; <= 0 omits it. Wire it to the EVENTS_RECONNECT_HINT config. Defaults to DefaultReconnectHint when unset.

Jump to

Keyboard shortcuts

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