eventing

package
v0.7.2 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 20 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, cron schedules, 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).

Types

type BlobEventData

type BlobEventData struct {
	Bucket  string    `json:"bucket"`
	Key     string    `json:"key"`
	Size    int64     `json:"size"`
	Version string    `json:"version"`
	Time    time.Time `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) 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) 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.

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.

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)
	Time            time.Time       `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
}

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.

type KVWatermark

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

KVWatermark is the V1 Watermark driver (ADR-0119, ADR-0157): one JSON SeenList per (ns, source, event) over the in-tree kvstore.KV substrate — durable get/put with its own backup path, so no second persistence mechanism.

func NewKVWatermark

func NewKVWatermark(kv kvstore.KV) (*KVWatermark, error)

NewKVWatermark builds the KV-backed Watermark driver over kv (required).

func (*KVWatermark) Delete added in v0.4.0

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

Delete removes every event record of one source.

func (*KVWatermark) ListSources added in v0.4.0

func (w *KVWatermark) ListSources(ctx context.Context) ([]SourceRef, error)

ListSources returns each source with at least one record, once: one List over the record prefix.

func (*KVWatermark) Load

func (w *KVWatermark) Load(ctx context.Context, ns v1.NamespaceName, source, event v1.ObjectName) (SeenList, error)

Load returns the persisted SeenList for (ns, source, event); a missing record is an empty one (no error).

func (*KVWatermark) Save

func (w *KVWatermark) Save(ctx context.Context, ns v1.NamespaceName, source, event v1.ObjectName, s SeenList) error

Save persists the SeenList for (ns, source, event), overwriting the prior record.

type MemWatermark

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

MemWatermark is the in-process, non-durable Watermark driver, held to the same contract as KVWatermark.

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) 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 kvstore.KV.

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 whose Interval has elapsed. Started by the pkg/funcd lifecycle (ADR-0033); not part of the reconciler.

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 V1 driver stores one JSON SeenList per (ns, source, event) over internal/kvstore.KV.

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 a DEDICATED instance (never the metastore).
Package badger is the dead-letter-queue deadletter driver (ADR-0118), backed by Badger v4 in a DEDICATED instance (never the metastore).
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.

Jump to

Keyboard shortcuts

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