eventing

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package eventing implements funcd eventing (ADR-0023, reshaped by ADR-0108): a typed CloudEvents v1.0 envelope, an EventSource reconciler that registers a `timer:` source's NAMED events, and — on each firing — PUBLISHES a named CloudEvent onto a Publisher seam (the in-process Fanout) the F69 Sensor subscribes to. It owns only KindEventSource (one-reconciler-per-gvk, ADR-0015); ticking is a side Run loop, not the reconciler. The action side (invoke a function / start a workflow) is the Sensor (ADR-0109); the webhook source kind and bus-backed delivery are documented deferrals.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseSourceURI

func ParseSourceURI(uri string) (ns v1.NamespaceName, source v1.ObjectName, ok bool)

ParseSourceURI is the inverse of SourceURI: it recovers (namespace, source) from a CloudEvent's `source` field so a subscriber can route by structured key (ADR-0108). ok=false for a foreign URI.

func SourceURI

func SourceURI(ns v1.NamespaceName, source v1.ObjectName) string

SourceURI is the canonical CloudEvent `source` for an EventSource (ADR-0108).

func WatermarkContract added in v0.11.0

func WatermarkContract(t *testing.T, newWatermark func(t *testing.T) Watermark)

WatermarkContract exercises the Watermark port against a driver (ADR-0157, ADR-0201): every driver's test runs it, so the event store's seen-list view on disk and in memory and MemWatermark are held to one behavior.

Types

type BlobEventData

type BlobEventData struct {
	Bucket  string       `json:"bucket"`
	Key     string       `json:"key"`
	Size    int64        `json:"size"`
	Version string       `json:"version"`
	Time    v1.Timestamp `json:"time"`
}

BlobEventData is the `data` payload of a blob CloudEvent (ADR-0119, F83): what landed under a watched Bucket prefix, for a Sensor to project (e.g. `${{ event.data.key }}`). Version is a (ModTime,Size) fingerprint of the observed object version — NOT a content ETag (a `data.etag` from blob.Attributes.MD5, ADR-0159, is ADR-0119's follow-up).

type BlobWatcher

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

BlobWatcher polls each registered blob EventSource, diffs each prefix against the persisted SeenList, and publishes one named CloudEvent per new or rewritten object through the existing ADR-0108 Publisher (ADR-0119, ADR-0157). Registration is driven by the KindEventSource reconciler (ADR-0015 one-per-gvk); Run is a side loop started by the pkg/funcd lifecycle. A key is recorded after its publish, so a crash between publish and save re-emits next poll (at-least-once; the Sensor/ADR-0118 absorb it).

func NewBlobWatcher

func NewBlobWatcher(lister BucketLister, pub Publisher, marks Watermark, interval time.Duration, log *slog.Logger) (*BlobWatcher, error)

NewBlobWatcher builds the watcher. lister/publisher/marks are required; interval ≤ 0 ⇒ the 15s default.

func (*BlobWatcher) ActiveWatches

func (w *BlobWatcher) ActiveWatches() int

ActiveWatches reports the number of registered named blob events (observability + the reconcile scenario).

func (*BlobWatcher) Advance added in v0.11.0

func (w *BlobWatcher) Advance(ctx context.Context, ns v1.NamespaceName, source v1.ObjectName) error

Advance marks every key the source's events list as seen and publishes nothing (ADR-0206 Decision 8), so the polls after a release replay none of them. A source with no registered event is fault.Unavailable: it is not Ready. Rerun-safe: each event's record is saved on its own.

func (*BlobWatcher) Deregister

func (w *BlobWatcher) Deregister(ns v1.NamespaceName, source v1.ObjectName)

Deregister removes every named event of one EventSource and keeps its records (BucketNotFound).

func (*BlobWatcher) Pending added in v0.11.0

func (w *BlobWatcher) Pending(ctx context.Context, ns v1.NamespaceName, source v1.ObjectName) (map[string]int, error)

Pending counts, per event of the source, the listed keys whose version its seen list lacks: what a release replays (ADR-0206 Decision 1). A source with no registered event has none.

func (*BlobWatcher) Purge added in v0.4.0

func (w *BlobWatcher) Purge(ctx context.Context, ns v1.NamespaceName, source v1.ObjectName) error

Purge removes every named event of one EventSource and deletes its records (delete, kind change). The incremented purge epoch makes a poll in flight stop without saving, also across a same-UID Register.

func (*BlobWatcher) Register

func (w *BlobWatcher) Register(ns v1.NamespaceName, source v1.ObjectName, uid v1.UID, bs *v1.BlobSource)

Register (re)registers every named event of a blob source and prunes events dropped from the spec — the reconciler calls it on a Ready blob EventSource. It stamps the source's current purge epoch into each entry and never increments it. A pruned event keeps its record until the source is purged (ADR-0157 Decision 5).

func (*BlobWatcher) Run

func (w *BlobWatcher) Run(ctx context.Context) error

Run sweeps orphan records once, then polls the registered set every interval until ctx is cancelled. Started by the pkg/funcd lifecycle beside the timer Source.Run; it is not part of the reconciler. While held it neither sweeps nor polls; the first tick after the release sweeps, then polls replay what the seen lists lack (ADR-0206 Decision 6).

func (*BlobWatcher) SetHold added in v0.11.0

func (w *BlobWatcher) SetHold(g hold.Gate)

SetHold installs the platform hold before Run (ADR-0206): while held Run neither sweeps nor polls.

func (*BlobWatcher) SetHooks added in v0.4.0

func (w *BlobWatcher) SetHooks(h WatchHooks)

SetHooks installs the store hooks the start sweep and the save-failure condition use.

func (*BlobWatcher) SetNamespaceHold added in v0.11.0

func (w *BlobWatcher) SetNamespaceHold(g hold.NamespaceGate)

SetNamespaceHold installs the per-namespace hold before Run (ADR-0222 Decision 8): a held namespace's sources are not polled.

type BucketLister

type BucketLister interface {
	List(ctx context.Context, ns v1.NamespaceName, bucket v1.ObjectName, prefix string) ([]blob.Attributes, error)
}

BucketLister resolves a Bucket resource to its scoped blob listing surface (ADR-0007/0080) and lists a prefix over it. The V1 driver reuses pkg/funcd's s3BucketFor resolver — the SAME blob.Bucket view external S3-frontend writes land in — so detection is writer-agnostic (it depends only on List). A missing Bucket resolves as fault.NotFound.

type CloudEvent

type CloudEvent struct {
	SpecVersion     string          `json:"specversion"`
	ID              string          `json:"id"`
	Source          string          `json:"source"`            // funcd://<ns>/eventsource/<name>
	Type            string          `json:"type"`              // the event name (ADR-0108)
	Subject         string          `json:"subject,omitempty"` // a SQL change's table (ADR-0235)
	Time            v1.Timestamp    `json:"time"`
	DataContentType string          `json:"datacontenttype,omitempty"`
	Data            json.RawMessage `json:"data,omitempty"`
}

CloudEvent is a CloudEvents v1.0 envelope serialized in the JSON event format. Hand-defined (no cloudevents/sdk-go); a later SDK swap stays behind this type.

func NewBlobEvent

func NewBlobEvent(ns v1.NamespaceName, source, event v1.ObjectName, d BlobEventData) (CloudEvent, error)

NewBlobEvent builds a named CloudEvent for a landed object (ADR-0119): the same envelope as NewNamedEvent (source URI carries ns+source, type carries the event name), but `data` is the object descriptor instead of the timer's empty `{}`. It flows the unchanged Fanout → Sensor path.

func NewNamedEvent

func NewNamedEvent(ns v1.NamespaceName, source, event v1.ObjectName) (CloudEvent, error)

NewNamedEvent builds a well-formed named CloudEvent for one event of an EventSource (ADR-0108): the `source` URI carries the namespace + source name, `type` carries the event name. A timer event has an empty `{}` payload. The id is a fresh random hex string (unique per call).

type Deps

type Deps struct {
	Store     store.Store  // required
	Publisher Publisher    // where a firing emits its named CloudEvent (required)
	Blob      *BlobWatcher // ADR-0119: the poll watcher a `blob:` source registers on; nil ⇒ no blob support
	Logger    *slog.Logger // default slog.Default()
	Clock     clock.Clock  // ADR-0182: the timer seed and the Run loop read it; default clock.System()
	// BucketRecheckInterval is eventing.bucketRecheckInterval (ADR-0163); 0 ⇒ bucketRecheckInterval.
	BucketRecheckInterval time.Duration
	// Hold is the platform hold (ADR-0206): while held the Run tick fires no timer; nil ⇒ never held.
	Hold hold.Gate
	// NamespaceHold is the per-namespace hold (ADR-0222 Decision 8): a held namespace's timers neither fire nor
	// advance; nil ⇒ none held.
	NamespaceHold hold.NamespaceGate
}

Deps configures the eventing Source (internal component, ADR-0002 §1; reshaped by ADR-0108, extended by ADR-0119).

type Fanout

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

Fanout is the in-process Publisher driver (ADR-0108): a fired CloudEvent is delivered to every live subscriber whose (namespace, source, event) key matches it. Routing derives that key from the envelope — ParseSourceURI(ev.Source) + ev.Type — the inverse of NewNamedEvent, so the URI is the single canonical name↔key mapping. The F69 Sensor subscribes; a firing to no subscriber is a no-op. Delivery is non-durable (a fire while a subscriber is down is lost); a bus-backed Publisher is a V2 swap.

func NewFanout

func NewFanout() *Fanout

NewFanout builds an empty in-process fanout publisher.

func (*Fanout) Publish

func (f *Fanout) Publish(ctx context.Context, ev CloudEvent) error

Publish delivers ev to every subscriber of its derived (ns, source, event) key. A source URI that is not a funcd EventSource URI is dropped (never an error) — the seam only carries funcd named events.

func (*Fanout) Subscribe

func (f *Fanout) Subscribe(ns v1.NamespaceName, source, event v1.ObjectName, fn func(context.Context, CloudEvent)) (cancel func())

Subscribe registers fn for the named event (ns, source, event); the returned cancel deregisters it.

func (*Fanout) Subscribed added in v0.13.0

func (f *Fanout) Subscribed(ns v1.NamespaceName, source, event v1.ObjectName) bool

Subscribed reports whether the named event (ns, source, event) has a subscriber, as the SQL change feed's boot gate asks (ADR-0235 Decision 4).

type MemWatermark

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

MemWatermark is the in-process, non-durable Watermark driver: the event store's seen lists in memory (ADR-0201).

func NewMemWatermark

func NewMemWatermark() *MemWatermark

NewMemWatermark builds an empty in-memory Watermark driver.

func (*MemWatermark) Delete added in v0.4.0

func (m *MemWatermark) Delete(_ context.Context, ns v1.NamespaceName, source v1.ObjectName) error

Delete removes every event record of one source.

func (*MemWatermark) ListSources added in v0.4.0

func (m *MemWatermark) ListSources(_ context.Context) ([]SourceRef, error)

ListSources returns each source with at least one record, once.

func (*MemWatermark) Load

func (m *MemWatermark) Load(_ context.Context, ns v1.NamespaceName, source, event v1.ObjectName) (SeenList, error)

Load returns a copy of the stored SeenList for (ns, source, event).

func (*MemWatermark) Range added in v0.11.0

func (m *MemWatermark) Range(fn func(ns v1.NamespaceName, source, event v1.ObjectName, s SeenList))

Range calls fn with a copy of every record, in no order, under the lock, so the records are one point in time (the event store's snapshot, ADR-0201 Decision 9); fn must not call m.

func (*MemWatermark) Save

func (m *MemWatermark) Save(_ context.Context, ns v1.NamespaceName, source, event v1.ObjectName, s SeenList) error

Save stores a copy of s for (ns, source, event).

type Publisher

type Publisher interface {
	Publish(ctx context.Context, ev CloudEvent) error
}

Publisher is the delivery seam a Source emits named CloudEvents to (ADR-0108). The V1 driver is the in-process Fanout (fanout.go) the F69 Sensor subscribes to; a bus-backed driver is a V2 swap. Emitting to nobody is a no-op by design (an EventSource with no Sensor yet is valid).

type SeenList added in v0.4.0

type SeenList struct {
	Bucket v1.ObjectName     `json:"bucket"`
	Prefix string            `json:"prefix"`
	UID    v1.UID            `json:"uid"`
	Seen   map[string]string `json:"seen"` // object key → the data.version it fired with (versionOf)
}

SeenList is the dedup record of one named blob event (ADR-0157), JSON in the event store (ADR-0201).

func LargeSeenList added in v0.11.0

func LargeSeenList(size int) SeenList

LargeSeenList returns a SeenList whose JSON is at least size bytes.

type Source

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

Source is the EventSource reconciler + the registered named-event timer set. A firing PUBLISHES a named CloudEvent (ADR-0108); the action side (invoke a function / start a workflow, and the Invocation record) is the F69 Sensor (ADR-0109). The Source no longer invokes.

func NewSource

func NewSource(d Deps) (*Source, error)

NewSource builds the Source. Store and Publisher are required.

func (*Source) ActiveTimers

func (s *Source) ActiveTimers() int

ActiveTimers reports the number of registered named-event timers (observability; a status/metrics surface and the reconcile scenario both read it).

func (*Source) Fire

func (s *Source) Fire(ctx context.Context, ns v1.NamespaceName, source, event v1.ObjectName) error

Fire performs one deterministic tick of a named event: build its CloudEvent and publish it. A published-to-nobody event is a no-op (no Sensor subscribed yet) — never an error.

func (*Source) Reconcile

func (s *Source) Reconcile(ctx context.Context, req controller.Request) (controller.Result, error)

Reconcile owns KindEventSource (ADR-0108): it registers every named event of a `timer:` source (→ Ready), deregisters on delete, and registers nothing for a source with no timer kind. A deleted or non-blob source has its blob records purged (ADR-0157).

func (*Source) Run

func (s *Source) Run(ctx context.Context) error

Run ticks the registered named-event set until ctx is cancelled, publishing each event that is due. Started by the pkg/funcd lifecycle (ADR-0033); not part of the reconciler. A tick while held skips dueTimers, so after the release each timer fires at most once for the periods it missed (ADR-0206 Decision 6).

type SourceRef added in v0.4.0

type SourceRef struct {
	Namespace v1.NamespaceName
	Name      v1.ObjectName
}

SourceRef names one EventSource that has blob event records.

type WatchHooks added in v0.4.0

type WatchHooks struct {
	Exists      func(ctx context.Context, src SourceRef) (bool, error)
	SaveFailing func(ctx context.Context, src SourceRef, failing map[v1.ObjectName]error) error // empty map clears
}

WatchHooks connect the BlobWatcher to the store; NewSource sets them before Run (ADR-0157 Decisions 8, 9).

type Watermark

type Watermark interface {
	// Load never returns a nil Seen: a missing record, or one saved with "seen":null, loads with an empty map.
	Load(ctx context.Context, ns v1.NamespaceName, source, event v1.ObjectName) (SeenList, error)
	Save(ctx context.Context, ns v1.NamespaceName, source, event v1.ObjectName, s SeenList) error
	// Delete removes every event record of one source; a source with no record is not an error.
	Delete(ctx context.Context, ns v1.NamespaceName, source v1.ObjectName) error
	// ListSources returns each source that has at least one record, once (the start sweep, ADR-0157 Decision 8).
	ListSources(ctx context.Context) ([]SourceRef, error)
}

Watermark persists per-event dedup state so a re-list and a restart never re-emit a seen object (ADR-0119, ADR-0157). The durable driver is the event store's seen-list view (ADR-0201, internal/eventing/eventstore).

Directories

Path Synopsis
Package deadletter is the eventing dead-letter queue PORT (ADR-0118, F85): the durable park for a Sensor action-delivery that failed past the bounded retry cap, or that was parked before its attempts ran out (ADR-0156: the Sensor's queue was full, the Sensor changed, or the daemon shut down).
Package deadletter is the eventing dead-letter queue PORT (ADR-0118, F85): the durable park for a Sensor action-delivery that failed past the bounded retry cap, or that was parked before its attempts ran out (ADR-0156: the Sensor's queue was full, the Sensor changed, or the daemon shut down).
badger
Package badger is the dead-letter-queue deadletter driver (ADR-0118), backed by Badger v4 in its own instance (never the metastore), which the event store shares with the blob seen lists (ADR-0201, NewOnDB).
Package badger is the dead-letter-queue deadletter driver (ADR-0118), backed by Badger v4 in its own instance (never the metastore), which the event store shares with the blob seen lists (ADR-0201, NewOnDB).
memory
Package memory is the in-memory deadletter.Store driver (ADR-0118): a small pure-Go map with no files, for cross-platform tests and the InMemory preset.
Package memory is the in-memory deadletter.Store driver (ADR-0118): a small pure-Go map with no files, for cross-platform tests and the InMemory preset.
Package eventstore is eventing's one durable store (ADR-0201): one Badger instance holding the Sensor's dead letters under dl/<ns>/<ulid> (ADR-0118), the BlobWatcher's seen lists under seen/ (ADR-0157) and the SQL change feed's watermarks under cdc/ (ADR-0235), each tenant iterating only its own prefix.
Package eventstore is eventing's one durable store (ADR-0201): one Badger instance holding the Sensor's dead letters under dl/<ns>/<ulid> (ADR-0118), the BlobWatcher's seen lists under seen/ (ADR-0157) and the SQL change feed's watermarks under cdc/ (ADR-0235), each tenant iterating only its own prefix.

Jump to

Keyboard shortcuts

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