README ¶
krt
: Kubernetes Declarative Controller Runtime
krt
provides a framework for building declarative controllers.
See the design doc and KubeCon talk for more background.
The framework aims to solve a few problems with writing controllers:
- Operate on any types, from any source. Kubernetes provides informers, but these only work on Kubernetes types read from Kubernetes objects.
krt
can accept any object type, from any source, and handle them the same.
- Provide high level abstractions.
- Controller authors can write simple transformation functions from
Input
->Output
(with dependencies); the framework handles all the state automatically.
- Controller authors can write simple transformation functions from
Key Primitives
The most important primitive provided is the Collection
interface.
This is basically an Informer
, but not tied to Kubernetes.
Currently, there are three ways to build a Collection
:
- Built from an
Informer
withWrapClient
orNewInformer
. - Statically configured with
NewStatic
. - Derived from other collections (more information on this below).
Unlike Informers
, these primitives work on arbitrary objects.
However, these objects are expected to have some properties, depending on their usage.
These are not expressed as generic constraints due to limitations in Go's type system.
- Each object
T
must have a uniqueKey[T]
(which is just a typed wrapper aroundstring
) that uniquely identifies the object. Default implementations exist for Kubernetes objects, Istioconfig.Config
objects, andResourceName() string
implementations. Equals(k K) bool
may be implemented to provide custom implementations to compare objects. Comparison is done to detect if changes were made. Default implementations are available for Kubernetes and protobuf objects, and will fallback toreflect.DeepEqual
.- A
Name
,Namespace
,Labels
, andLabelSelector
may optionally be included, for use with filters (see below).
Derived Collections
The core of the framework is in the ability to derive collections from others.
In general, these are built by providing some func(inputs...) outputs...
(called "transformation" functions).
While more could be expressed, there are currently three forms implemented.
func() *O
viaNewSingleton
- This generates a collection that has a single value. An example would be some global configuration.
func(input I) *O
viaNewCollection
- This generates a one-to-one mapping of input to output. An example would be a transformation from a
Pod
type to a genericWorkload
type.
- This generates a one-to-one mapping of input to output. An example would be a transformation from a
func(input I) []O
viaNewManyCollection
- This generates a one-to-many mapping of input to output. An example would be a transformation from a
Service
to a set ofEndpoint
types. - The order of the response does not matter. Each response must have a unique key.
- This generates a one-to-many mapping of input to output. An example would be a transformation from a
The form used and input type only represent the primary dependencies, indicating the cardinality. Each transformation can additionally include an arbitrary number of dependencies, fetching data from other collections.
For example, a simple Singleton
example that keeps track of the number of ConfigMap
s in the cluster:
ConfigMapCount := krt.NewSingleton[int](func(ctx krt.HandlerContext) *int {
cms := krt.Fetch(ctx, ConfigMaps)
return ptr.Of(len(cms))
})
The Fetch
operation enables querying against other collections.
If the result of the Fetch
operation changes, the collection will automatically be recomputed; the framework handles the state and event detection.
In the above example, the provided function will be called (at least) every time there is a change to a configmap.
The ConfigMapCount
collection will produce events only when the count changes.
The framework will use generic Equals on the underlying object to determine whether or not to recompute collections.
Picking a collection type
There are a variety of collection types available. Picking these is about simplicity, usability, and performance.
The NewSingleton
form (func() *O
), in theory, could be used universally.
Consider a transformation from Pod
to SimplePod
:
SimplePods := krt.NewSingleton[SimplePod](func(ctx krt.HandlerContext) *[]SimplePod {
res := []SimplePod{}
for _, pod := range krt.Fetch(ctx, Pod) {
res = append(res, SimplePod{Name: pod.Name})
}
return &res
}) // Results in a Collection[[]SimplePod]
While this works, it is inefficient and complex to write.
Consumers of SimplePod can only query the entire list at once.
Anytime any Pod
changes, all SimplePod
s must be recomputed.
A better approach would be to lift Pod
into a primary dependency:
SimplePods := krt.NewCollection[SimplePod](func(ctx krt.HandlerContext, pod *v1.Pod) *SimplePod {
return &SimplePod{Name: pod.Name}
}) // Results in a Collection[SimplePod]
Not only is this simpler to write, its far more efficient.
Consumers can more efficiently query for SimplePod
s using label selectors, filters, etc.
Additionally, if a single Pod
changes we only recompute one SimplePod
.
Above we have a one-to-one mapping of input and output.
We may have one-to-many mappings, though.
In these cases, usually its best to use a ManyCollection
.
Like the above examples, its possible to express these as normal Collection
s, but likely inefficient.
Example computing a list of all container names across all pods:
ContainerNames := krt.NewManyCollection[string](func(ctx krt.HandlerContext, pod *v1.Pod) (res []string) {
for _, c := range pod.Spec.Containers {
res = append(res, c.Name)
}
return res
}) // Results in a Collection[string]
Example computing a list of service endpoints, similar to the Kubernetes core endpoints controller:
Endpoints := krt.NewManyCollection[Endpoint](func(ctx krt.HandlerContext, svc *v1.Service) (res []Endpoint) {
for _, c := range krt.Fetch(ctx, Pods, krt.FilterLabel(svc.Spec.Selector)) {
res = append(res, Endpoint{Service: svc.Name, Pod: pod.Name, IP: pod.status.PodIP})
}
return res
}) // Results in a Collection[Endpoint]
As a rule of thumb, if your Collection
type is a list, you most likely should be using a different type to flatten the list.
An exception to this would be if the list represents an atomic set of items that are never queried independently;
in these cases, however, it is probably best to wrap it in a struct.
For example, to represent the set of containers in a pod, we may make a type PodContainers struct { Name string, Containers []string }
and have a
Collection[PodContainers]
rather than a Collection[[]string]
.
In theory, other forms could be expressed such as func(input1 I1, input2 I2) *O
.
However, there haven't yet been use cases for these more complex forms.
Transformation constraints
In order for the framework to properly handle dependencies and events, transformation functions must adhere by a few properties.
Basically, Transformations must be stateless and idempotent.
- Any querying of other
Collection
s must be done throughkrt.Fetch
. - Querying other data stores that may change is not permitted.
- Querying external state (e.g. making HTTP calls) is not permitted.
- Transformations may be called at any time, including many times for the same inputs. Transformation functions should not make any assumptions about calling patterns.
Violation of these properties will result in undefined behavior (which would likely manifest as stale data).
Fetch details
In addition to simply fetching all resources from a collection, a filter can be provided.
This is more efficient than filtering outside of Fetch
, as the framework can filter un-matched objects earlier, skipping redundant work.
The following filters are provided
FilterName(name, namespace)
: filters an object by Name and Namespace.FilterNamespace(namespace)
: filters an object by Namespace.FilterKey(key)
: filters an object by key.FilterLabel(labels)
: filters to only objects that match these labels.FilterSelects(labels)
: filters to only objects that select these labels. An empty selector matches everything.FilterSelectsNonEmpty(labels)
: filters to only objects that select these labels. An empty selector matches nothing.FilterGeneric(func(any) bool)
: filters by an arbitrary function.
Note that most filters may only be used if the objects being Fetch
ed implement appropriate functions to extract the fields filtered against.
Failures to meet this requirement will result in a panic
.
Library Status
This library is currently "experimental" and is not used in Istio production yet. The intent is this will be slowly rolled out to controllers that will benefit from it and are lower risk; likely, the ambient controller will be the first target.
While its plausible all of Istio could be fundamentally re-architected to fully embrace krt
throughout (replacing things like PushContext
),
it is not yet clear this is desired.
Performance
Compared to a perfectly optimized hand-written controller, krt
adds some overhead.
However, writing a perfectly optimized controller is hard, and often not done.
As a result, for many scenarios it is expected that krt
will perform on-par or better.
This is similar to a comparison between a high level programming language compared to assembly;
while its always possible to write better code in assembly, smart compilers can make optimizations humans are unlikely to,
such as loop unrolling.
Similarly, krt
can make complex optimizations in one place, so each controller implementation doesn't, which is likely to increase
the amount of optimizations applied.
The BenchmarkControllers
puts this to the test, comparing an ideal hand-written controller to one written in krt
.
While the numbers are likely to change over time, at the time of writing the overhead for krt
is roughly 10%:
name time/op
Controllers/krt-8 13.4ms ±23%
Controllers/legacy-8 11.4ms ± 6%
name alloc/op
Controllers/krt-8 15.2MB ± 0%
Controllers/legacy-8 12.9MB ± 0%
Future work
Object optimizations
One important aspect of krt
is its ability to automatically detect if objects have changed, and only trigger dependencies if so.
This works better when we only compare fields we actually use.
Today, users can do this manually by making a transformation from the full object to a subset of the object.
This could be improved by:
- Automagically detecting which subset of the object is used, and optimize this behind the scenes. This seems unrealistic, though.
- Allow a lightweight form of a
Full -> Subset
transformation, that doesn't create a full new collection (with respective overhead), but rather overlays on top of an existing one.
Internal dependency optimizations
Today, the library stores a mapping of Input -> Dependencies
(map[Key[I]][]dependency
).
Often times, there are common dependencies amongst keys.
For example, a namespace filter probably has many less unique values than unique input objects.
Other filters may be completely static and shared by all keys.
This could be improved by:
- Optimize the data structure to be a bit more advanced in sharing dependencies between keys.
- Push the problem to the user; allow them to explicitly set up static
Fetch
es.
Debug tooling
krt
has an opportunity to add a lot of debugging capabilities that are hard to do elsewhere, because it would require
linking up disparate controllers, and a lot of per-controller logic.
Some debugging tooling ideas:
- Add OpenTelemetry tracing to controllers (prototype).
- Automatically generate mermaid diagrams showing system dependencies.
- Automatically detect violations of Transformation constraints.
Documentation ¶
Index ¶
- Variables
- func BatchedEventFilter[I, O any](cf func(a I) O, handler func(events []Event[I], initialSync bool)) func(o []Event[I], initialSync bool)
- func Fetch[T any](ctx HandlerContext, cc Collection[T], opts ...FetchOption) []T
- func FetchOne[T any](ctx HandlerContext, c Collection[T], opts ...FetchOption) *T
- func GetApplyConfigKey[O any](a O) *string
- func GetKey[O any](a O) string
- type Collection
- func JoinCollection[T any](cs []Collection[T], opts ...CollectionOption) Collection[T]
- func NewCollection[I, O any](c Collection[I], hf TransformationSingle[I, O], opts ...CollectionOption) Collection[O]
- func NewInformer[I controllers.ComparableObject](c kube.Client, opts ...CollectionOption) Collection[I]
- func NewInformerFiltered[I controllers.ComparableObject](c kube.Client, filter kubetypes.Filter, opts ...CollectionOption) Collection[I]
- func NewManyCollection[I, O any](c Collection[I], hf TransformationMulti[I, O], opts ...CollectionOption) Collection[O]
- func NewManyFromNothing[O any](hf TransformationEmptyToMulti[O], opts ...CollectionOption) Collection[O]
- func WrapClient[I controllers.ComparableObject](c kclient.Informer[I], opts ...CollectionOption) Collection[I]
- type CollectionDump
- type CollectionOption
- type DebugCollection
- type DebugHandler
- type Equaler
- type Event
- type EventStream
- type FetchOption
- func FilterGeneric(f func(any) bool) FetchOption
- func FilterIndex[K comparable, I any](idx Index[K, I], k K) FetchOption
- func FilterKey(k string) FetchOption
- func FilterKeys(k ...string) FetchOption
- func FilterLabel(lbls map[string]string) FetchOption
- func FilterObjectName(name types.NamespacedName) FetchOption
- func FilterSelects(lbls map[string]string) FetchOption
- func FilterSelectsNonEmpty(lbls map[string]string) FetchOption
- type HandlerContext
- type Index
- type InputDump
- type Key
- type LabelSelectorer
- type Labeler
- type Named
- type Namer
- type Namespacer
- type RecomputeTrigger
- type ResourceNamer
- type Singleton
- type StaticCollection
- func (s StaticCollection) DeleteObject(k string)
- func (s StaticCollection[T]) DeleteObjects(filter func(obj T) bool)
- func (s StaticCollection) GetKey(k string) *T
- func (s StaticCollection) List() []T
- func (s StaticCollection) Register(f func(o Event[T])) Syncer
- func (s StaticCollection) RegisterBatch(f func(o []Event[T], initialSync bool), runExistingState bool) Syncer
- func (s StaticCollection) Synced() Syncer
- func (s StaticCollection) UpdateObject(obj T)
- type StaticSingleton
- type Syncer
- type TestingDummyContext
- type TransformationEmpty
- type TransformationEmptyToMulti
- type TransformationMulti
- type TransformationSingle
Constants ¶
This section is empty.
Variables ¶
var GlobalDebugHandler = new(DebugHandler)
Functions ¶
func BatchedEventFilter ¶
func BatchedEventFilter[I, O any](cf func(a I) O, handler func(events []Event[I], initialSync bool)) func(o []Event[I], initialSync bool)
BatchedEventFilter allows an event handler to have alternative event suppression mechanics to filter out unnecessary events. For instance, I can make a transformation from `object => object.name` to only trigger events for changes to the name; the output will be compared (using standard equality checking), and only changes will trigger the handler. Note this is in addition to the normal event mechanics, so this can only filter things further.
func Fetch ¶
func Fetch[T any](ctx HandlerContext, cc Collection[T], opts ...FetchOption) []T
func FetchOne ¶
func FetchOne[T any](ctx HandlerContext, c Collection[T], opts ...FetchOption) *T
func GetApplyConfigKey ¶
GetApplyConfigKey returns the key for the ApplyConfig. If there is none, this will return nil.
Types ¶
type Collection ¶
type Collection[T any] interface { // GetKey returns an object by its key, if present. Otherwise, nil is returned. GetKey(k string) *T // List returns all objects in the collection. // Order of the list is undefined. List() []T EventStream[T] }
Collection is the core resource type for krt, representing a collection of objects. Items can be listed, or fetched directly. Most importantly, consumers can subscribe to events when objects change.
func JoinCollection ¶
func JoinCollection[T any](cs []Collection[T], opts ...CollectionOption) Collection[T]
JoinCollection combines multiple Collection[T] into a single Collection[T] merging equal objects into one record in the resulting Collection
func NewCollection ¶
func NewCollection[I, O any](c Collection[I], hf TransformationSingle[I, O], opts ...CollectionOption) Collection[O]
NewCollection transforms a Collection[I] to a Collection[O] by applying the provided transformation function. This applies for one-to-one relationships between I and O. For zero-to-one, use NewSingleton. For one-to-many, use NewManyCollection.
func NewInformer ¶
func NewInformer[I controllers.ComparableObject](c kube.Client, opts ...CollectionOption) Collection[I]
NewInformer creates a Collection[I] sourced from the results of kube.Client querying resources of type I from the API Server.
Resources must have their GVR and GVK registered in the kube.Client before this method is called, otherwise NewInformer will panic.
func NewInformerFiltered ¶
func NewInformerFiltered[I controllers.ComparableObject](c kube.Client, filter kubetypes.Filter, opts ...CollectionOption) Collection[I]
NewInformerFiltered takes an argument that filters the results from the kube.Client. Otherwise, behaves the same as NewInformer
func NewManyCollection ¶
func NewManyCollection[I, O any](c Collection[I], hf TransformationMulti[I, O], opts ...CollectionOption) Collection[O]
NewManyCollection transforms a Collection[I] to a Collection[O] by applying the provided transformation function. This applies for one-to-many relationships between I and O. For zero-to-one, use NewSingleton. For one-to-one, use NewCollection.
func NewManyFromNothing ¶
func NewManyFromNothing[O any](hf TransformationEmptyToMulti[O], opts ...CollectionOption) Collection[O]
NewManyFromNothing is a niche Collection type that doesn't have any input dependencies. This is useful where things only rely on out-of-band data via RecomputeTrigger, for instance.
func WrapClient ¶
func WrapClient[I controllers.ComparableObject](c kclient.Informer[I], opts ...CollectionOption) Collection[I]
WrapClient is the base entrypoint that enables the creation of a collection from an API Server client.
Generic types can use kclient.NewDynamic to create an informer for a Collection of type controllers.Object
type CollectionDump ¶
type CollectionOption ¶
type CollectionOption func(*collectionOptions)
CollectionOption is a functional argument type that can be passed to Collection constructors.
func WithDebugging ¶
func WithDebugging(handler *DebugHandler) CollectionOption
WithDebugging enables debugging of the collection
func WithName ¶
func WithName(name string) CollectionOption
WithName allows explicitly naming a controller. This is a best practice to make debugging easier. If not set, a default name is picked.
func WithObjectAugmentation ¶
func WithObjectAugmentation(fn func(o any) any) CollectionOption
WithObjectAugmentation allows transforming an object into another for usage throughout the library. Currently this applies to things like Name, Namespace, Labels, LabelSelector, etc. Equals is not currently supported, but likely in the future. The intended usage is to add support for these fields to collections of types that do not implement the appropriate interfaces. The conversion function can convert to a embedded struct with extra methods added:
type Wrapper struct { Object } func (w Wrapper) ResourceName() string { return ... } WithObjectAugmentation(func(o any) any { return Wrapper{o.(Object)} })
func WithStop ¶
func WithStop(stop <-chan struct{}) CollectionOption
WithStop sets a custom stop channel so a collection can be terminated when the channel is closed
type DebugCollection ¶
type DebugCollection struct {
// contains filtered or unexported fields
}
func (DebugCollection) MarshalJSON ¶
func (p DebugCollection) MarshalJSON() ([]byte, error)
type DebugHandler ¶
type DebugHandler struct {
// contains filtered or unexported fields
}
DebugHandler allows attaching a variety of collections to it and then dumping them
func (*DebugHandler) MarshalJSON ¶
func (p *DebugHandler) MarshalJSON() ([]byte, error)
type Equaler ¶
Equaler is an optional interface that can be implemented by collection types. If implemented, this will be used to determine if an object changed.
type Event ¶
type Event[T any] struct { // Old object, set on Update or Delete. Old *T // New object, set on Add or Update New *T // Event is the change type Event controllers.EventType }
Event represents a point in time change for a collection.
type EventStream ¶
type EventStream[T any] interface { // Register adds an event watcher to the collection. Any time an item in the collection changes, the handler will be // called. Typically, usage of Register is done internally in krt via composition of Collections with Transformations // (NewCollection, NewManyCollection, NewSingleton); however, at boundaries of the system (connecting to something not // using krt), registering directly is expected. // Handlers have the following semantics: // * On each event, all handlers are called. // * Each handler has its own unbounded event queue. Slow handlers will cause this queue to accumulate, but will not block // other handlers. // * Events will be sent in order, and will not be dropped or deduplicated. Register(f func(o Event[T])) Syncer // Synced returns a Syncer which can be used to determine if the collection has synced. Once its synced, all dependencies have // been processed, and all handlers have been called with the results. Synced() Syncer // RegisterBatch registers a handler that accepts multiple events at once. This can be useful as an optimization. // Otherwise, behaves the same as Register. // Additionally, skipping the default behavior of "send all current state through the handler" can be turned off. // This is important when we register in a handler itself, which would cause duplicative events. // Handlers MUST not mutate the event list. RegisterBatch(f func(o []Event[T], initialSync bool), runExistingState bool) Syncer }
EventStream provides a link between the underlying collection and its clients. The EventStream does not publish events for retrigger operations where the resultant object of type T is equal to an existing object in the collection.
On initial sync, events will be published to registered clients as the Collection is populated.
type FetchOption ¶
type FetchOption func(*dependency)
FetchOption is a functional argument type that can be passed to Fetch. These are all created by the various Filter* functions
func FilterGeneric ¶
func FilterGeneric(f func(any) bool) FetchOption
func FilterIndex ¶
func FilterIndex[K comparable, I any](idx Index[K, I], k K) FetchOption
FilterIndex selects only objects matching a key in an index.
func FilterKey ¶
func FilterKey(k string) FetchOption
func FilterKeys ¶
func FilterKeys(k ...string) FetchOption
func FilterLabel ¶
func FilterLabel(lbls map[string]string) FetchOption
FilterLabel only includes objects that match the provided labels. If the selector is empty, it IS a match.
func FilterObjectName ¶
func FilterObjectName(name types.NamespacedName) FetchOption
FilterObjectName selects a Kubernetes object by name.
func FilterSelects ¶
func FilterSelects(lbls map[string]string) FetchOption
FilterSelects only includes objects that select this label. If the selector is empty, it is a match.
func FilterSelectsNonEmpty ¶
func FilterSelectsNonEmpty(lbls map[string]string) FetchOption
FilterSelectsNonEmpty only includes objects that select this label. If the selector is empty, it is NOT a match.
type HandlerContext ¶
type HandlerContext interface {
// contains filtered or unexported methods
}
HandlerContext is an opaque type passed into transformation functions. This can be used with Fetch to dynamically query for resources. Note: this doesn't expose Fetch as a method, as Go generics do not support arbitrary generic types on methods.
type Index ¶
type Index[K comparable, O any] interface { Lookup(k K) []O // contains filtered or unexported methods }
func NewIndex ¶
func NewIndex[K comparable, O any]( c Collection[O], extract func(o O) []K, ) Index[K, O]
NewIndex creates a simple index, keyed by key K, over an informer for O. This is similar to Informer.AddIndex, but is easier to use and can be added after an informer has already started.
func NewNamespaceIndex ¶
func NewNamespaceIndex[O Namespacer](c Collection[O]) Index[string, O]
NewNamespaceIndex is a small helper to index a collection by namespace
type LabelSelectorer ¶
LabelSelectorer is an optional interface that can be implemented by collection types. If implemented, this will be used to determine an objects' LabelSelectors
type Labeler ¶
Labeler is an optional interface that can be implemented by collection types. If implemented, this will be used to determine an objects' Labels
type Named ¶
type Named struct {
Name, Namespace string
}
Named is a convenience struct. It is ideal to be embedded into a type that has a name and namespace, and will automatically implement the various interfaces to return the name, namespace, and a key based on these two.
func (Named) GetNamespace ¶
func (Named) ResourceName ¶
type Namer ¶
type Namer interface {
GetName() string
}
Namer is an optional interface that can be implemented by collection types. If implemented, this will be used to determine an objects' Name.
type Namespacer ¶
type Namespacer interface {
GetNamespace() string
}
Namespacer is an optional interface that can be implemented by collection types. If implemented, this will be used to determine an objects' Namespace.
type RecomputeTrigger ¶
type RecomputeTrigger struct {
// contains filtered or unexported fields
}
RecomputeTrigger trigger provides an escape hatch to allow krt transformations to depend on external state and recompute correctly when those change. Typically, all state is registered and fetched through krt.Fetch. Through this mechanism, any changes are automatically propagated through the system to dependencies. In some cases, it may not be feasible to get all state into krt; hopefully, this is a temporary state. RecomputeTrigger works around this by allowing an explicit call to recompute a collection; the caller must be sure to call Trigger() any time the state changes.
func NewRecomputeTrigger ¶
func NewRecomputeTrigger(startSynced bool, opts ...CollectionOption) *RecomputeTrigger
func (*RecomputeTrigger) MarkDependant ¶
func (r *RecomputeTrigger) MarkDependant(ctx HandlerContext)
MarkDependant marks the given context as depending on this trigger. This registers it to be recomputed when TriggerRecomputation is called.
func (*RecomputeTrigger) MarkSynced ¶
func (r *RecomputeTrigger) MarkSynced()
MarkSynced marks this trigger as ready. Before this is called, dependant collections will be blocked. This ensures initial state is populated.
func (*RecomputeTrigger) TriggerRecomputation ¶
func (r *RecomputeTrigger) TriggerRecomputation()
TriggerRecomputation tells all dependants to recompute
type ResourceNamer ¶
type ResourceNamer interface {
ResourceName() string
}
ResourceNamer is an optional interface that can be implemented by collection types. If implemented, this can be used to determine the Key for an object
type Singleton ¶
type Singleton[T any] interface { // Get returns the object, or nil if there is none. Get() *T // Register adds an event watcher to the object. Any time it changes, the handler will be called Register(f func(o Event[T])) Syncer AsCollection() Collection[T] }
Singleton is a special Collection that only ever has a single object. They can be converted to the Collection where convenient, but when using directly offer a more ergonomic API
func NewSingleton ¶
func NewSingleton[O any](hf TransformationEmpty[O], opts ...CollectionOption) Singleton[O]
type StaticCollection ¶
type StaticCollection[T any] struct { // contains filtered or unexported fields }
func NewStaticCollection ¶
func NewStaticCollection[T any](vals []T, opts ...CollectionOption) StaticCollection[T]
func (StaticCollection) DeleteObject ¶
func (s StaticCollection) DeleteObject(k string)
DeleteObject deletes an object from the collection.
func (StaticCollection[T]) DeleteObjects ¶
func (s StaticCollection[T]) DeleteObjects(filter func(obj T) bool)
DeleteObjects deletes all objects matching the provided filter
func (StaticCollection) RegisterBatch ¶
func (StaticCollection) UpdateObject ¶
func (s StaticCollection) UpdateObject(obj T)
UpdateObject adds or updates an object into the collection.
type StaticSingleton ¶
func NewStatic ¶
func NewStatic[T any](initial *T, startSynced bool, opts ...CollectionOption) StaticSingleton[T]
type TestingDummyContext ¶
type TestingDummyContext struct{}
type TransformationEmpty ¶
type TransformationEmpty[T any] func(ctx HandlerContext) *T
TransformationEmpty represents a singleton operation. There is always a single output. Note this can still depend on other types, via Fetch.
type TransformationEmptyToMulti ¶
type TransformationEmptyToMulti[T any] func(ctx HandlerContext) []T
TransformationEmptyToMulti represents a singleton operator that returns a set of objects. There are no inputs.
type TransformationMulti ¶
type TransformationMulti[I, O any] func(ctx HandlerContext, i I) []O
TransformationMulti represents a one-to-many relationship between I and O.
type TransformationSingle ¶
type TransformationSingle[I, O any] func(ctx HandlerContext, i I) *O
TransformationSingle represents a one-to-one relationship between I and O.