refreshable

package module
v2.9.0 Latest Latest
Warning

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

Go to latest
Published: Mar 25, 2026 License: BSD-3-Clause Imports: 10 Imported by: 7

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Cached added in v2.1.0

func Cached[T any](original Refreshable[T]) (Refreshable[T], UnsubscribeFunc)

Cached returns a new Refreshable that subscribes to the original Refreshable and caches its value. This is useful in combination with View to avoid recomputing an expensive mapped value each time it is retrieved. The returned refreshable is read-only (does not implement Update).

Example
r := refreshable.New(42)
cached, stop := refreshable.Cached(r)
cached.Subscribe(func(val int) {
	fmt.Println("Update:", val)
})
fmt.Println(cached.Current())
r.Update(100)
fmt.Println(cached.Current())
r.Update(100) // No update
stop()
r.Update(200)
fmt.Println(cached.Current())
Output:
Update: 42
42
Update: 100
100
100

func Collect added in v2.1.0

func Collect[T any](list ...Refreshable[T]) (Refreshable[[]T], UnsubscribeFunc)

Collect returns a new Refreshable that combines the latest values of multiple Refreshables into a slice. The returned Refreshable is updated whenever any of the original Refreshables updates.

Example
r1 := refreshable.New(10)
r2 := refreshable.New(20)
r3 := refreshable.New(30)

collected, stop := refreshable.Collect(r1, r2, r3)

printCollected := func() {
	values := collected.Current()
	fmt.Printf("Collected values: %v\n", values)
}

printCollected() // Initial values

r1.Update(15)
printCollected() // After updating r1

r2.Update(25)
printCollected() // After updating r2

r3.Update(35)
printCollected() // After updating r3

stop() // Stop collecting updates

r1.Update(40)
printCollected() // No change after stopping
Output:
Collected values: [10 20 30]
Collected values: [15 20 30]
Collected values: [15 25 30]
Collected values: [15 25 35]
Collected values: [15 25 35]

func CollectMutable added in v2.5.0

func CollectMutable[T any](list ...Refreshable[T]) (Refreshable[[]T], AddFunc[T], UnsubscribeFunc)

CollectMutable returns a new Refreshable that combines the latest values of multiple Refreshables into a slice. The returned Refreshable is updated whenever any of the Refreshables updates. The add function allows adding new Refreshables to the collection after creation. The unsubscribe function removes subscriptions from all Refreshables in the collection.

Example
r1 := refreshable.New(10)
r2 := refreshable.New(20)

collected, add, stop := refreshable.CollectMutable(r1, r2)

printCollected := func() {
	values := collected.Current()
	fmt.Printf("Collected values: %v\n", values)
}

printCollected() // Initial values

// Dynamically add a new refreshable
r3 := refreshable.New(30)
add(r3)
printCollected() // After adding r3

// Updates propagate from all refreshables
r1.Update(15)
printCollected() // After updating r1

r3.Update(35)
printCollected() // After updating r3

stop() // Stop collecting updates

r1.Update(40)
printCollected() // No change after stopping
Output:
Collected values: [10 20]
Collected values: [10 20 30]
Collected values: [15 20 30]
Collected values: [15 20 35]
Collected values: [15 20 35]

func CollectValidated added in v2.7.0

func CollectValidated[T any](list ...Validated[T]) (Validated[[]T], UnsubscribeFunc)

CollectValidated returns a new Validated that combines the latest values of multiple Validated refreshables into a slice. The returned Validated is updated whenever any of the original Validated refreshables updates.

func CollectValidatedMutable added in v2.7.0

func CollectValidatedMutable[T any](list ...Validated[T]) (Validated[[]T], ValidatedAddFunc[T], UnsubscribeFunc)

CollectValidatedMutable returns a new Validated that combines the latest values of multiple Validated refreshables into a slice. The returned Validated is updated whenever any of the Validated refreshables updates. The add function allows adding new Validated refreshables to the collection after creation. The unsubscribe function removes subscriptions from all Validated refreshables in the collection.

func Map

func Map[T any, M any](original Refreshable[T], mapFn func(T) M) (Refreshable[M], UnsubscribeFunc)

Map returns a new Refreshable based on the current one that handles updates based on the current Refreshable. See Cached and View for more information.

Example
r := refreshable.New(42)
mapped, stop := refreshable.Map(r, func(val int) string {
	return fmt.Sprintf("Mapped: %d", val)
})
fmt.Println(mapped.Current())
r.Update(100)
fmt.Println(mapped.Current())
stop()
r.Update(200)
fmt.Println(mapped.Current())
Output:
Mapped: 42
Mapped: 100
Mapped: 100

func MapFromValidated added in v2.7.1

func MapFromValidated[T any, M any](original Validated[T], mapFn func(T) M) (Refreshable[M], UnsubscribeFunc)

MapFromValidated returns a new Refreshable by applying mapFn to the most recent value to pass validation from the original Validated. Invalid updates are ignored.

func MapFromValidatedChecked added in v2.8.0

func MapFromValidatedChecked[T any, M any](original Validated[T], mapFn func(T) M) (Refreshable[M], UnsubscribeFunc, error)

MapFromValidatedChecked is identical to MapFromValidated but first checks if the original Validated currently has a validation error and returns it if so.

func MapValidated added in v2.7.0

func MapValidated[T any, M any](ctx context.Context, original Validated[T], mapFn func(context.Context, T) (M, error)) (Validated[M], UnsubscribeFunc, error)

MapValidated returns a new Validated based on the current one that handles updates based on the current Validated. The context is passed to the mapFn but is not considered in the subscription lifecycle. An error is returned if the current original value fails to map. The subscription and mapping continue until the UnsubscribeFunc is called or the Validated is garbage-collected.

func MapWithError

func MapWithError[T any, M any](ctx context.Context, original Refreshable[T], mapFn func(context.Context, T) (M, error)) (Validated[M], UnsubscribeFunc, error)

MapWithError is similar to Validate but allows for the function to return a mapping/mutation of the input object in addition to returning an error. The returned validRefreshable will contain the mapped value. The context is passed to the mapFn but is not considered in the subscription lifecycle. An error is returned if the current original value fails to map. The subscription and mapping continue until the UnsubscribeFunc is called or the Validated is garbage-collected.

Example
ctx := context.Background()
r := refreshable.New(42)
validated, stop, err := refreshable.MapWithError(ctx, r, func(_ context.Context, val int) (string, error) {
	if val < 50 {
		return "", fmt.Errorf("invalid: %d", val)
	}
	return fmt.Sprintf("Valid: %d", val), nil
})
if err != nil {
	fmt.Println("Initial error:", err)
}
fmt.Println(validated.Validation())
r.Update(100)
fmt.Println(validated.Validation())
r.Update(24)
fmt.Println(validated.Validation())
stop()
r.Update(200)
fmt.Println(validated.Validation())
Output:
Initial error: invalid: 42
 invalid: 42
Valid: 100 <nil>
 invalid: 24
 invalid: 24

func Merge added in v2.1.0

func Merge[T1 any, T2 any, R any](original1 Refreshable[T1], original2 Refreshable[T2], mergeFn func(T1, T2) R) (Refreshable[R], UnsubscribeFunc)

Merge returns a new Refreshable that combines the latest values of two Refreshables of different types using the mergeFn. The returned Refreshable is updated whenever either of the original Refreshables updates.

Example
r1 := refreshable.New(42)
r2 := refreshable.New(100)
merged, stop := refreshable.Merge(r1, r2, func(v1, v2 int) string {
	return fmt.Sprintf("Sum: %d", v1+v2)
})
fmt.Println(merged.Current())
r1.Update(50)
fmt.Println(merged.Current())
r2.Update(150)
fmt.Println(merged.Current())
stop()
r1.Update(60)
fmt.Println(merged.Current())
Output:
Sum: 142
Sum: 150
Sum: 200
Sum: 200

func MergeValidated added in v2.7.0

func MergeValidated[T1 any, T2 any, R any](original1 Validated[T1], original2 Validated[T2], mergeFn func(T1, T2) R) (Validated[R], UnsubscribeFunc)

MergeValidated returns a new Validated that combines the latest values of two Validated refreshables using the mergeFn.

func MergeValidatedAndRefreshable added in v2.7.1

func MergeValidatedAndRefreshable[T1 any, T2 any, R any](
	ctx context.Context,
	original1 Validated[T1],
	refreshable1 Refreshable[T2],
	mergeFn func(T1, T2) R,
) (Validated[R], UnsubscribeFunc)

MergeValidatedAndRefreshable returns a new Validated that combines the latest values of a Validated and a plain Refreshable using the mergeFn. The Refreshable is wrapped with an always-valid Validate so that only errors from the Validated source propagate. The returned Validated is updated whenever either source updates. The context is used internally to wrap the Refreshable as a Validated but does not affect the subscription lifecycle. The subscription and mapping continue until the UnsubscribeFunc is called or the Validated is garbage-collected.

func NewFromTickerFunc

func NewFromTickerFunc[T any](ctx context.Context, interval time.Duration, provider func(ctx context.Context) (T, bool)) (Ready[T], UnsubscribeFunc)

NewFromTickerFunc returns a Ready Refreshable populated by the result of the provider called each interval. If the providers bool return is false, the value is ignored. The result's ReadyC channel is closed when a new value is populated. The refreshable will stop updating when the provided context is cancelled or the returned UnsubscribeFunc func is called.

func Validate

func Validate[T any](ctx context.Context, original Refreshable[T], validatingFn func(context.Context, T) error) (Validated[T], UnsubscribeFunc, error)

Validate returns a new Refreshable that returns the latest original value accepted by the validatingFn. The context is passed to the validatingFn but is not considered in the subscription lifecycle. If the upstream value results in an error, it is reported by Validation(). An error is returned if the current original value is invalid. The subscription and mapping continue until the UnsubscribeFunc is called or the Validated is garbage-collected.

Example
ctx := context.Background()
r := refreshable.New(42)
validated, stop, err := refreshable.Validate(ctx, r, func(_ context.Context, val int) error {
	if val < 50 {
		return errors.New("value too low")
	}
	return nil
})
if err != nil {
	fmt.Println("Initial error:", err)
}
fmt.Println(validated.Validation())
r.Update(100)
fmt.Println(validated.Validation())
stop()
r.Update(200)
fmt.Println(validated.Validation())
Output:
Initial error: value too low
0 value too low
100 <nil>
100 <nil>

func Wait

func Wait[T any](ctx context.Context, ready Ready[T]) (T, bool)

Wait waits until the Ready has a current value or the context expires.

Types

type AddFunc added in v2.5.0

type AddFunc[T any] func(Refreshable[T])

AddFunc is a function that adds a new Refreshable to a collection.

type ChangeDetector added in v2.7.0

type ChangeDetector interface {
	// ShouldUpdate returns true if the data source appears to have changed
	// since the last call to MarkUpdated, or if the change status cannot be determined.
	ShouldUpdate(ctx context.Context) bool
	// MarkUpdated commits the pending state from the last ShouldUpdate call,
	// so that subsequent ShouldUpdate calls compare against it.
	MarkUpdated()
}

ChangeDetector determines whether an underlying data source has changed since the last successful read. Implementations handle internal bookkeeping of previous state.

func NewAlwaysCheckChangeDetector added in v2.7.0

func NewAlwaysCheckChangeDetector() ChangeDetector

type Ready

type Ready[T any] interface {
	Refreshable[T]
	// ReadyC returns a channel which is closed after a value is successfully populated.
	ReadyC() <-chan struct{}
}

Ready extends Refreshable for asynchronous implementations which may not have a value when they are constructed. Callers should check that the Ready channel is closed before using the Current value.

func NewFromChannel

func NewFromChannel[T any](values <-chan T) Ready[T]

NewFromChannel populates an Updatable with the values channel. If an element is already available, the returned Value is guaranteed to be populated. The channel should be closed when no longer used to avoid leaking resources.

type Refreshable

type Refreshable[T any] interface {
	// Current returns the most recent value of this Refreshable.
	// If the value has not been initialized, returns T's zero value.
	Current() T

	// Subscribe calls the consumer function when Value updates until stop is closed.
	// The consumer must be relatively fast: Updatable.Set blocks until all subscribers have returned.
	// Expensive or error-prone responses to refreshed values should be asynchronous.
	// Updates considered no-ops by reflect.DeepEqual may be skipped.
	// When called, consumer is executed with the Current value.
	Subscribe(consumer func(T)) UnsubscribeFunc
}

A Refreshable is a generic container type for a volatile underlying value. It supports atomic access and user-provided callback "subscriptions" on updates.

func CachedAuto added in v2.9.0

func CachedAuto[T any](original Refreshable[T]) Refreshable[T]

CachedAuto is like Cached with automatic GC-based cleanup of the upstream subscription.

func CollectAuto added in v2.9.0

func CollectAuto[T any](list ...Refreshable[T]) Refreshable[[]T]

CollectAuto is like Collect with automatic GC-based cleanup of the upstream subscriptions.

func MapAuto added in v2.9.0

func MapAuto[T any, M any](original Refreshable[T], mapFn func(T) M) Refreshable[M]

MapAuto is like Map with automatic GC-based cleanup of the upstream subscription.

func MapContext

func MapContext[T any, M any](ctx context.Context, original Refreshable[T], mapFn func(T) M) Refreshable[M]

MapContext is like Map but unsubscribes when the context is cancelled.

Example
ctx, cancel := context.WithCancel(context.Background())
r := refreshable.New(42)
mapped := refreshable.MapContext(ctx, r, func(val int) string {
	return fmt.Sprintf("Mapped: %d", val)
})
fmt.Println(mapped.Current())
r.Update(100)
fmt.Println(mapped.Current())
cancel()
time.Sleep(time.Millisecond)
runtime.Gosched()
r.Update(200)
fmt.Println(mapped.Current())
Output:
Mapped: 42
Mapped: 100
Mapped: 100

func MapFromValidatedAuto added in v2.9.0

func MapFromValidatedAuto[T any, M any](original Validated[T], mapFn func(T) M) Refreshable[M]

MapFromValidatedAuto is like MapFromValidated with automatic GC-based cleanup of the upstream subscription.

func MapFromValidatedCheckedAuto added in v2.9.0

func MapFromValidatedCheckedAuto[T any, M any](original Validated[T], mapFn func(T) M) (Refreshable[M], error)

MapFromValidatedCheckedAuto is like MapFromValidatedChecked with automatic GC-based cleanup of the upstream subscription.

func MergeAuto added in v2.9.0

func MergeAuto[T1 any, T2 any, R any](original1 Refreshable[T1], original2 Refreshable[T2], mergeFn func(T1, T2) R) Refreshable[R]

MergeAuto is like Merge with automatic GC-based cleanup of the upstream subscriptions.

func View added in v2.1.0

func View[T any, M any](original Refreshable[T], mapFn func(T) M) Refreshable[M]

View returns a Refreshable implementation that converts the original Refreshable value to a new value using mapFn. Current() and Subscribe() invoke mapFn as needed on the current value of the original Refreshable. Subscription callbacks are invoked with the mapped value each time the original value changes and the result is not cached nor compared for equality with the previous value, so functions subscribing to View refreshables are more likely to receive duplicate updates.

Example
r := refreshable.New(42)
view := refreshable.View(r, func(val int) string {
	return fmt.Sprintf("Value: %d", val)
})
fmt.Println(view.Current())
r.Update(100)
fmt.Println(view.Current())
r.Update(100) // Duplicate update
fmt.Println(view.Current())
Output:
Value: 42
Value: 100
Value: 100

type UnsubscribeFunc

type UnsubscribeFunc func()

UnsubscribeFunc removes a subscription from a refreshable's internal tracking and/or stops its update routine. It is safe to call multiple times.

type Updatable

type Updatable[T any] interface {
	Refreshable[T]
	// Update updates the Refreshable with a new T.
	// It blocks until all subscribers have completed.
	Update(T)
}

A Updatable is a Refreshable which supports setting the value with a user-provided value. When a utility returns a (non-Updatable) Refreshable, it implies that value updates are handled internally.

func New

func New[T any](val T) Updatable[T]

New returns a new Updatable that begins with the given value.

Example
r := refreshable.New(42)
fmt.Println(r.Current())
Output:
42

type Validated

type Validated[T any] interface {
	// SubscribeValidated calls the consumer function when the validated value updates until stop is closed.
	// The consumer receives the latest value and its validation error (nil if valid).
	SubscribeValidated(consumer func(Validated[T])) UnsubscribeFunc
	// Unvalidated returns the most recent value to pass validation.
	Unvalidated() T
	// Validation returns the result of the most recent validation.
	// If the last value was valid, Validation returns the same value as Unvalidated and a nil error.
	// If the last value was invalid, Validation returns T's zero value and the error. Unvalidated returns the most recent valid value.
	Validation() (T, error)
}

A Validated is capable of rejecting updates according to validation logic. Its Unvalidated method returns the most recent value to pass validation.

func CollectValidatedAuto added in v2.9.0

func CollectValidatedAuto[T any](list ...Validated[T]) Validated[[]T]

CollectValidatedAuto is like CollectValidated with automatic GC-based cleanup of the upstream subscriptions.

func MapValidatedAuto added in v2.9.0

func MapValidatedAuto[T any, M any](ctx context.Context, original Validated[T], mapFn func(context.Context, T) (M, error)) (Validated[M], error)

MapValidatedAuto is like MapValidated with automatic GC-based cleanup of the upstream subscription.

func MapValues added in v2.4.0

func MapValues[K comparable, V, R any](
	ctx context.Context,
	refreshableMap Refreshable[map[K]V],
	mapperFn func(context.Context, K, V) Validated[R],
) Validated[map[K]R]

MapValues creates a Validated[map[K]R] by applying mapperFn to each entry of a map Refreshable. When keys are added, mapperFn is called with a per-key context that is cancelled on removal. When any per-key refreshable updates, the output map is rebuilt with aggregated validation errors.

Use this instead of Map on a map[K]V when you need per-key refreshables that update independently.

func MapWithErrorAuto added in v2.9.0

func MapWithErrorAuto[T any, M any](ctx context.Context, original Refreshable[T], mapFn func(context.Context, T) (M, error)) (Validated[M], error)

MapWithErrorAuto is like MapWithError with automatic GC-based cleanup of the upstream subscription.

func MergeValidatedAndRefreshableAuto added in v2.9.0

func MergeValidatedAndRefreshableAuto[T1 any, T2 any, R any](
	ctx context.Context,
	original1 Validated[T1],
	refreshable1 Refreshable[T2],
	mergeFn func(T1, T2) R,
) Validated[R]

MergeValidatedAndRefreshableAuto is like MergeValidatedAndRefreshable with automatic GC-based cleanup of the upstream subscriptions.

func MergeValidatedAuto added in v2.9.0

func MergeValidatedAuto[T1 any, T2 any, R any](original1 Validated[T1], original2 Validated[T2], mergeFn func(T1, T2) R) Validated[R]

MergeValidatedAuto is like MergeValidated with automatic GC-based cleanup of the upstream subscriptions.

func NewFileRefreshable added in v2.3.0

func NewFileRefreshable(ctx context.Context, filePath string) Validated[[]byte]

NewFileRefreshable creates a Validated refreshable that reads from a file every second. It is equivalent to calling NewFileRefreshableWithTicker with time.Tick(time.Second).

func NewFileRefreshableWithReaderFunc added in v2.6.0

func NewFileRefreshableWithReaderFunc(ctx context.Context, filePath string, updateTicker <-chan time.Time, readerFuncOld func(string) ([]byte, error)) Validated[[]byte]

NewFileRefreshableWithReaderFunc returns a Validated refreshable whose current value is the bytes read using the provided readerFunc. This function is similar to NewFileRefreshableWithTicker but allows callers to provide a custom file reading function instead of using os.ReadFile directly. This is useful for scenarios where custom file processing is needed (e.g., decompression, decryption, or other transformations).

The readerFunc is called once initially and then on each tick until the context is cancelled. If reading fails, the Unvalidated() value will be unchanged. The error is present in v.Validation().

func NewFileRefreshableWithTicker added in v2.3.0

func NewFileRefreshableWithTicker(ctx context.Context, filePath string, updateTicker <-chan time.Time) Validated[[]byte]

NewFileRefreshableWithTicker returns a Validated refreshable whose current value is the bytes of the file at the provided path. This function reads the file once then starts a goroutine which re-reads the file on each tick until the provided context is cancelled. If reading the file fails, the Unvalidated() value will be unchanged. The error is present in v.Validation(). It is equivalent to calling NewFileRefreshableWithReaderFunc with os.ReadFile.

func NewMultiFileRefreshable added in v2.4.0

func NewMultiFileRefreshable(ctx context.Context, paths Refreshable[map[string]struct{}]) Validated[map[string][]byte]

NewMultiFileRefreshable creates a Validated Refreshable that tracks the contents of multiple files. The input is a Refreshable of a set of file paths (map keys). The output is a Validated Refreshable of a map from file path to file contents. When files are added to or removed from the input set, the corresponding file watchers are created or destroyed. Each file is read periodically using NewFileRefreshable.

Unvalidated() returns a map containing the last successfully read content for each file. Validation() returns the map and a joined error of all file read failures.

func NewRefreshableTicker added in v2.7.0

func NewRefreshableTicker[M any](ctx context.Context, updateTicker <-chan time.Time, readerFunc func(context.Context) (M, error), detector ChangeDetector) Validated[M]

NewRefreshableTicker returns a Validated refreshable whose current value is read using the provided readerFunc. The readerFunc is only called when the ChangeDetector indicates the data source has changed. The detector's MarkUpdated is called after each successful read. The readerFunc is called once initially and then on each tick (subject to the detector) until the context is cancelled or the returned Validated is garbage collected. If reading fails, the Unvalidated() value will be unchanged. The error is present in v.Validation().

func NewRefreshableTickerWithDuration added in v2.7.0

func NewRefreshableTickerWithDuration[M any](ctx context.Context, a time.Duration, readerFunc func(context.Context) (M, error), detector ChangeDetector) Validated[M]

func ValidateAuto added in v2.9.0

func ValidateAuto[T any](ctx context.Context, original Refreshable[T], validatingFn func(context.Context, T) error) (Validated[T], error)

ValidateAuto is like Validate with automatic GC-based cleanup of the upstream subscription.

type ValidatedAddFunc added in v2.7.0

type ValidatedAddFunc[T any] func(Validated[T])

ValidatedAddFunc is a function that adds a new Validated to a collection.

Jump to

Keyboard shortcuts

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