realtime

package
v0.8.5 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package realtime serves subscriptions to document changes as server-sent events (design/triggers-and-realtime.md §17 and §20). It is driver-neutral and knows nothing of HTTP: the Hub owns the limits, buffers and the shared change feed; a Stream is one open connection with its subscriptions; the httpapi package turns requests into streams and frames into bytes.

Index

Constants

View Source
const (
	EventSessionEnded = "session_ended" // the session, user or key is gone: sign in again
	EventReauth       = "reauth"        // the credential's lifetime is over: reconnect with a fresh one
)

Events that end a stream (design §17.2).

View Source
const (
	ReasonHistoryExpired = "history_expired" // the change history no longer reaches the stream
	ReasonAccessChanged  = "access_changed"  // the subscriber's access changed (design §19)
	ReasonSlowConsumer   = "slow_consumer"   // the stream's buffer was dropped
	ReasonPreimageMiss   = "preimage_missing"
	ReasonConfigChanged  = "config_changed" // the configuration the subscription depended on changed: subscribe again
)

Reset reasons: what a `reset` event says about why a client has to reload.

Variables

View Source
var (
	ErrTooManyStreams = errors.New("realtime: this instance has too many open streams")
	ErrUserStreams    = errors.New("realtime: too many open streams for this caller")
	ErrOpenTooFast    = errors.New("realtime: streams are opened too fast")
)

Refusals when a stream can't be opened. Each says when to try again.

View Source
var DefaultLimits = Limits{
	SubscriptionsPerStream: 20, StreamsPerUser: 5, Streams: 10000, BufferBytes: 256 << 10, BufferEvents: 1000,
	Memory: 256 << 20, Heartbeat: 25 * time.Second, OpenInterval: time.Second, ResumeMaxAge: 24 * time.Hour,
}

DefaultLimits are used for what is zero.

View Source
var ErrBadToken = errors.New("realtime: invalid resume token")

ErrBadToken means a resume token is malformed, was not signed with this realm's key, or belongs to another realm or principal.

View Source
var ErrTooManySubscriptions = errors.New("realtime: too many subscriptions")

ErrTooManySubscriptions is returned when a request has more subscriptions than allowed.

View Source
var Heartbeat = Frame{}

Heartbeat is the frame that keeps an idle connection alive.

Functions

func CollKey

func CollKey(db, coll string) string

CollKey is how ConfigChanged names a collection.

func Seal

func Seal(key []byte, t Token) string

Seal renders t as text: the payload and its MAC.

Types

type Access

type Access struct {
	SessionID string // the session's id (its document key)
	UserID    string
	KeyID     string // an API key's document key (its hash)
	Orgs      []string
	// Fingerprint summarizes what the caller's access depends on (roles, memberships, second
	// factor, consents). Resume tokens carry it: replaying history made while the access was
	// different would show states the caller couldn't see then.
	Fingerprint string
	// UsesConfig says a subscribed collection's rules read the app configuration, so a change of
	// the configuration changes what is visible.
	UsesConfig bool
	// Expires is when the credential stops being good; zero: never.
	Expires time.Time
	// Reload reads the caller's access again and returns what each subscription (by id) shows
	// now. Ended means the credential is no longer valid.
	Reload func(ctx context.Context) (Reloaded, error)
}

Access ties a stream to who is on the other end, so changes of that access reach it.

type Frame

type Frame struct {
	ID    string // a resume token; set on the last frame of a change
	Event string
	Data  []byte
}

Frame is one server-sent event. Event "" with no data is a heartbeat.

type Hub

type Hub struct {
	// Feed is the instance's shared consumer; every stream is a handler of it.
	Feed *changefeed.Lazy
	// Replay makes a consumer that reads from a resume position, over the same set the Feed
	// reads. Nil disables resuming: a token is taken as expired history.
	Replay func() *changefeed.Consumer
	// ReloadJitter spreads the reads of access after a change (up to this long, per stream; default
	// 250ms, negative for none) and ReloadConcurrency bounds how many run at once (default 16).
	ReloadJitter time.Duration
	// RefreshEvery is how often an open stream reads its caller's access again with nothing having
	// changed: access that ends by the clock (a role window, a rule on `now`) has no event.
	// Default 5 minutes; negative disables it.
	RefreshEvery      time.Duration
	ReloadConcurrency int
	Limits            Limits
	Observer          Observer
	Now               func() time.Time
	// contains filtered or unexported fields
}

Hub holds the open streams of one instance.

func (*Hub) Buffered

func (h *Hub) Buffered() int64

Buffered is the bytes held in all buffers.

func (*Hub) Change

func (h *Hub) Change(_ context.Context, c changefeed.Change, at changefeed.Position)

Change is the hub as the feed's handler: it finds, once per change, the subscriptions the change may matter to, and hands each stream its own.

func (*Hub) ConfigChanged

func (h *Hub) ConfigChanged(colls map[string]bool)

ConfigChanged tells the streams that subscriptions to these collections (keyed by MongoDB database and collection) no longer follow the configuration they were opened with: they are removed from their streams and the client is told to subscribe again.

func (*Hub) Gap

func (h *Hub) Gap(context.Context, changefeed.Gap)

Gap is called when the feed lost history: whatever the streams missed is gone.

func (*Hub) Idle

Idle is part of changefeed.Handler: nothing to deliver.

func (*Hub) Open

func (h *Hub) Open(req Request) (*Stream, error)

Open registers a stream. The caller writes `ready`, then calls Run, and always Close.

func (*Hub) Streams

func (h *Hub) Streams() int

Streams is how many streams are open.

type Key

type Key struct {
	Field  string
	Values []any
}

Key is an equality a subscription can only be visible under: every document it shows has one of Values at Field. It is what lets a change find the subscriptions it can matter to without asking all of them.

func KeysOf

func KeysOf(f storage.Filter) []Key

KeysOf extracts the keys of a filter: its top-level conditions (under And) that are an equality or a membership on a field. A condition under Or or Not is not a key, because the subscription may be visible without it. The result is only a shortcut: the whole filter still decides every candidate.

type Limits

type Limits struct {
	SubscriptionsPerStream int
	StreamsPerUser         int
	Streams                int // per instance
	BufferBytes            int // per stream
	BufferEvents           int // per stream
	Memory                 int64
	Heartbeat              time.Duration
	// OpenInterval is the least time between two streams opened by one principal.
	OpenInterval time.Duration
	// ResumeMaxAge is how old a resume token may be before the history is taken as gone.
	ResumeMaxAge time.Duration
}

Limits are what bounds a deployment's streams.

func (Limits) OrDefault

func (l Limits) OrDefault() Limits

OrDefault fills the zero fields.

type Observer

type Observer interface {
	StreamOpened(realm string)
	StreamClosed(realm string)
	Event(realm, event string)
	Reset(realm, reason string)
	Refused(realm, reason string)
	// Checks counts the subscriptions a change was evaluated against: kind "indexed" (found by
	// document id or key) or "scanned" (no key: asked on every change).
	Checks(realm, kind string, n int)
}

Observer receives the realtime metrics; every method may be called on a nil receiver by its implementation.

type Reloaded

type Reloaded struct {
	Ended       bool
	Fingerprint string
	Orgs        []string
	Expires     time.Time
	Subs        map[string]Visibility
}

Reloaded is the answer of Access.Reload.

type Request

type Request struct {
	Realm string
	// Principal names who the stream is for ("user:u_1", "key:ci", "anon:<ip>"); streams per
	// caller and resume tokens are counted and bound by it.
	Principal     string
	Key           []byte // the realm's resume key
	Subscriptions []*Subscription
	Resume        string // a token, or ""
	// Limits lower the instance's: per stream subscriptions and streams per user, from the realm.
	MaxStreamsPerUser int
	// Access ties the stream to its caller's session, user or key, so that changes of them
	// reach it; nil for anonymous callers and realms without authentication.
	Access *Access
}

Request opens a stream.

type Stream

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

Stream is one open connection. It is a changefeed.Handler of the instance's shared feed.

func (*Stream) Close

func (s *Stream) Close()

Close ends the stream and releases what it holds. It is safe to call more than once.

func (*Stream) Done

func (s *Stream) Done() <-chan struct{}

Done is closed when the stream is closed.

func (*Stream) IDs

func (s *Stream) IDs() []string

IDs are the stream's subscription ids, in the order they were asked for.

func (*Stream) Run

func (s *Stream) Run(ctx context.Context, w func(Frame) error) error

Run writes the stream's frames with w until ctx is done, w fails or the stream is closed.

type Subscription

type Subscription struct {
	ID string
	// DB and Coll name what a change reports: the MongoDB database and the collection.
	DB, Coll string
	Doc      string // a document id; "" follows the collection
	Visible  func(doc map[string]any) (bool, error)
	// Render turns a stored document into what a read returns.
	Render func(doc map[string]any) map[string]any
	// Keys are the equalities the subscription can only be visible under (KeysOf of its
	// resolved filter): they let a change find it without asking every subscription.
	Keys []Key
	// contains filtered or unexported fields
}

Subscription is one thing a stream follows: a collection filtered by Where, or one document of it. Visible is what decides, for the stream's caller, whether a document is part of it: the subscription's filter, the collection's read rule and soft deletion, evaluated in memory on the document as stored.

type Token

type Token struct {
	Pos       []byte `json:"p"`
	Realm     string `json:"r"`
	Principal string `json:"u"`
	Issued    int64  `json:"t"` // unix seconds
	// Access fingerprints what the caller could see when the token was issued; a resume under
	// another one (a role or membership changed meanwhile) reloads instead of replaying.
	Access string `json:"a,omitempty"`
}

Token is a place in the change history that a client can resume after. It is signed with a per-realm key, so any instance can verify it after a reconnect and nobody can forge one or present another's.

func Open

func Open(key []byte, s string) (Token, error)

Open verifies and decodes a token made by Seal.

type Visibility

type Visibility struct {
	// Changed says the caller's effective access to the subscription differs from before.
	Changed bool
	Visible func(doc map[string]any) (bool, error)
	Keys    []Key
}

Visibility is what decides, after a reload, what a subscription shows.

Jump to

Keyboard shortcuts

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