cache

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package cache binds semantic cache declarations to concrete Go key and value types. Applications use Cache methods; serialized bytes remain in Backend adapters. A missing entry is distinct from a stored zero value and an error.

Index

Constants

View Source
const (
	MaxMemoEntries = 256
	MaxMemoBytes   = 1 << 20
)

Memo bounds: a full memo stops recording; reads then use the Store as usual.

View Source
const (
	KeyFeature          keyspace.Feature = "cache"
	NamespaceKeyFeature keyspace.Feature = "cache-namespace"
)

Physical address families include the namespace generation metadata used by tags.

View Source
const MaxBatchEntries = 256

MaxBatchEntries bounds input keys before canonicalization. Config may lower it.

View Source
const MaxKeyBytes = keyspace.MaxKeyBytes
View Source
const MaxLoadTimeout = 24 * time.Hour

MaxLoadTimeout bounds how long a detached Remember loader can own its fill.

View Source
const MaxTags = 64

MaxTags bounds application tag references per operation. One additional reserved namespace stamp is permitted. Config.MaxTags may impose a smaller application limit.

View Source
const TagVersionBytes = 16

Variables

This section is empty.

Functions

func FillLeaseKey

func FillLeaseKey(fill EntryKey) (lease.Key, error)

FillLeaseKey is the adapter address for a fill identity. Tagged callers use TaggedKey.FillKey, so a new tag generation has independent ownership.

func ValidateBatchKeys

func ValidateBatchKeys(keys []EntryKey) error

ValidateBatchKeys validates an adapter batch's namespace, order and uniqueness. Empty input is valid; the adapter still validates its lifecycle and context.

func ValidateFillProof

func ValidateFillProof(ctx context.Context, fill EntryKey, proof lease.Proof) error

ValidateFillProof checks cancellation, local validity and the exact cache-fill binding. Adapters must additionally compare ownership atomically at publication.

func ValidateTagKeys

func ValidateTagKeys(keys []EntryKey) error

ValidateTagKeys validates the bounded, canonical address sequence accepted by TaggedBackend metadata operations. Adapter implementations share this contract; their lifecycle and context checks remain adapter-owned. Input is not mutated.

func ValidateTaggedBatch

func ValidateTaggedBatch(keys []TaggedKey) error

ValidateTaggedBatch requires bounded canonical data addresses and the same exact immutable tag stamps on every entry, so one atomic metadata check covers a batch.

func WithMemo

func WithMemo(ctx context.Context) context.Context

WithMemo returns a context that memoizes typed cache reads for its lifetime, typically one request. Within it, the first Get, GetMany, Exists or Remember of a key reads the Store; later reads of that key through the same context (and its children) return the memoized result without I/O, even if another request or process has since changed the entry. Writes (Put, PutMany, Add, Forget, ForgetMany, Increment, Expire) through the context forget the key's memoized results, and Store.Invalidate/InvalidateTags forget the whole store's. At most MaxMemoEntries results and MaxMemoBytes of payload are memoized; later reads are not recorded. A context that already memoizes is returned unchanged. Each result still decodes its own copy.

Types

type Backend

type Backend interface {
	Get(context.Context, EntryKey) ([]byte, bool, error)
	Put(context.Context, EntryKey, []byte, TTL) error
	Add(context.Context, EntryKey, []byte, TTL) (bool, error)
	Forget(context.Context, EntryKey) (bool, error)
}

Backend is the serialized cache adapter boundary, not an application value API. Implementations must be concurrency-safe, observe cancellation, copy retained input bytes without modifying the caller's input and return owned read bytes. Get distinguishes absence from failure; a present empty byte slice is a hit. Add checks absence and writes atomically. A failed mutating command may have reached a remote backend: never retry it implicitly. Adapter lifecycle is owned by the caller that constructs the backend. Store borrows this capability and does not close a shared adapter.

type BatchBackend

type BatchBackend interface {
	ForgetMany(context.Context, []EntryKey) (uint64, error)
}

BatchBackend removes a bounded canonical batch in one atomic operation. All validation precedes deletion. Count distinct live entries; preserve all live entries if a later key fails validation. Errors return zero, but a remote error may hide an applied whole batch. Adapters validate with ValidateBatchKeys.

type BatchReadBackend

type BatchReadBackend interface {
	GetMany(context.Context, []EntryKey) ([]BatchValue, error)
}

BatchReadBackend reads a bounded canonical batch (see ValidateBatchKeys) in one operation, returning results in input order. Missing, expired or unusable entries are misses. Size bounds follow Get; nothing is mutated except removal of unusable storage.

type BatchValue

type BatchValue struct {
	Data  []byte
	Found bool
}

BatchValue is one adapter batch-read result. Found distinguishes a miss from a present empty payload; Data is owned by the caller.

type Cache

type Cache[K, V any] struct {
	// contains filtered or unexported fields
}

Cache accepts only its declared key and value types. Values are serialized snapshots; reading a hit does not return a reference into backend storage. The zero value rejects operations. Use Declaration.Bind to construct a handle.

func (Cache[K, V]) Add

func (c Cache[K, V]) Add(ctx context.Context, key K, value V, ttl TTL) (bool, error)

Add atomically inserts only when no unexpired entry exists. False means an existing value was retained, including its original TTL. Encoding precedes Add.

func (Cache[K, V]) Exists

func (c Cache[K, V]) Exists(ctx context.Context, key K) (bool, error)

Exists checks whether a live entry belongs to the current namespace/tag snapshot. It does not decode or copy the value; it is not validation of the application DTO. A later Get can miss after expiry/invalidation or fail its own codec/size checks. Like Get, a tagged existence check that races an invalidation re-resolves its snapshot (bounded) and otherwise reports false; it never returns Conflict.

func (Cache[K, V]) Expire

func (c Cache[K, V]) Expire(ctx context.Context, key K, ttl TTL) (bool, error)

Expire updates an existing entry's expiry without changing its value. Forever removes expiry. A present entry returns true even if the TTL did not change; absent/invalidated entries return false. Invalid TTLs fail before I/O. This is atomic with snapshot validation, not with a subsequent Get or application write.

func (Cache[K, V]) Flexible

func (c Cache[K, V]) Flexible(ctx context.Context, key K, fresh, stale time.Duration, loader func(context.Context) (V, error), options ...RememberOption) (V, error)

Flexible is Remember with stale-while-revalidate. A value younger than fresh is returned as is. An older value, up to fresh+stale, is returned immediately while one Store-owned background fill (coalesced with other fills of the key) loads and publishes a replacement; its failure is only logged (WithLogger). A missing or expired value is loaded like Remember and stored for fresh+stale.

Freshness is a separate marker entry written after the value with TTL fresh and read with it in one batch, so no clock comparison is involved. The marker is an ordinary stored entry: it counts toward adapter capacity (MaxEntries and MaxBytes of file and PostgreSQL stores) and invalidation removes it with the value. The value stays an ordinary entry of this family (Get, Forget and invalidation apply). Put, PutMany, Add, Increment and Expire leave the marker unchanged: a value written while a marker lives counts as fresh until that marker expires; without one it is served stale and refreshed. Loader, timeout, coalescing and publication rules are those of Remember. The background fill keeps the caller's values but not its cancellation. Flexible reads are not memoized by WithMemo.

func (Cache[K, V]) Forget

func (c Cache[K, V]) Forget(ctx context.Context, key K) (bool, error)

func (Cache[K, V]) ForgetMany

func (c Cache[K, V]) ForgetMany(ctx context.Context, keys ...K) (uint64, error)

ForgetMany atomically removes distinct live entries from this typed view. The input count is bounded before key encoding/deduplication. All keys are encoded before metadata I/O, and one snapshot protects the entire batch. No payload codec runs. An empty batch validates handle/context but performs no backend I/O. A failed remote acknowledgement returns zero and may hide an applied batch. Like Forget, this does not prevent a concurrent Remember from later repopulating.

func (Cache[K, V]) Get

func (c Cache[K, V]) Get(ctx context.Context, key K) (V, bool, error)

Get returns found=true for every stored value, including nil or a zero V. Backend/decoder failure returns zero V, false and an error; it is never a miss. A tagged read that races an invalidation re-resolves its snapshot (bounded) and otherwise reports a miss; a pure read never returns a snapshot conflict. A stored value larger than the current MaxValueBytes (written under an earlier, larger bound) is a miss that a later write replaces.

func (Cache[K, V]) GetMany

func (c Cache[K, V]) GetMany(ctx context.Context, keys ...K) ([]Lookup[V], error)

GetMany reads up to Config.MaxBatchEntries keys under one tag snapshot and returns results in input order (a repeated key repeats its result). Adapters implementing BatchReadBackend/TaggedBatchReadBackend (memory and Redis) read the whole batch in one operation; others read each distinct key in turn. Like Get, a snapshot replaced by a concurrent invalidation is re-resolved (bounded) and otherwise every key is a miss. Each value is decoded independently.

func (Cache[K, V]) Pull

func (c Cache[K, V]) Pull(ctx context.Context, key K) (V, bool, error)

Pull reads and removes one entry. It is not atomic: a concurrent writer can replace the value between the read and the removal, which then removes the replacement. Use it for single-consumer values such as one-time notices.

func (Cache[K, V]) Put

func (c Cache[K, V]) Put(ctx context.Context, key K, value V, ttl TTL) error

Put replaces one entry with the supplied TTL. No implicit command retry occurs.

func (Cache[K, V]) PutMany

func (c Cache[K, V]) PutMany(ctx context.Context, ttl TTL, entries ...Entry[K, V]) error

PutMany writes up to Config.MaxBatchEntries entries with one TTL. Every key and value is encoded before the first write, so a codec failure writes nothing. Entries then use one tag snapshot (adapters with SnapshotWriteBackend resolve the current snapshot inside each write). The batch is not atomic: entries are written in input order (a repeated key keeps its last value), the first failure stops the remaining writes, and earlier entries stay stored. No write is retried; Config.Timeout bounds the whole batch.

func (Cache[K, V]) Remember

func (c Cache[K, V]) Remember(ctx context.Context, key K, ttl TTL, loader func(context.Context) (V, error), options ...RememberOption) (V, error)

Remember reads a snapshot or coalesces concurrent misses within this Store. The selected owner supplies the loader and TTL; followers do not run their loaders. Each result is decoded independently, including the owner's.

The loader runs in a fill owned by the Store, under a context that keeps the owner's values but not its cancellation, bounded by LoadTimeout (or WithLoadTimeout). A canceled caller, owner or follower, stops only its own wait; the fill finishes for everyone else and keeps its MaxFills slot until the loader actually exits. If a loader fails only because it used the owner's own (ended) request context, followers elect a new owner instead of failing. Other loader failures reach existing followers without retry.

Config.Timeout bounds each backend step, not the loader. A failed publication of a successfully loaded value does not fail the call: every caller receives the value, and the failure is counted in Stats and logged (WithLogger). When MaxFills is exhausted, the caller loads directly without coalescing rather than failing; a full MaxFillWaiters returns retryable fault.Overloaded.

A fill rechecks storage after election, but is not a transaction with Put or Forget: overlapping writes may be replaced and forgotten entries repopulated. NewCoordinatedStore adds lease-protected publication across instances. Ordinary stores coordinate locally. Neither mode promises exactly-once loaders. Recursive loads must propagate the supplied context; detected fill cycles fail with fault.Cycle.

func (Cache[K, V]) WithTags

func (c Cache[K, V]) WithTags(first Tag, rest ...Tag) (Cache[K, V], error)

WithTags returns a view with the supplied tag set; it replaces any earlier set. At least one tag is required. Construction validates ownership/capability without encoding keys or performing I/O. Operations share the same typed API and Store.

type Codec

type Codec[V any] struct {
	// contains filtered or unexported fields
}

Codec preserves V through serialization. Callbacks must be concurrency-safe, leave inputs unchanged and return owned results. The Cache copies buffers at adapter boundaries and catches codec panic/Goexit without publishing a value. Size limits bound serialized storage, not arbitrary allocations inside a codec.

func JSON

func JSON[V any]() Codec[V]

JSON uses Go's JSON encoding rules without declaring a public HTTP DTO schema. It preserves dynamic JSON numbers as json.Number and rejects trailing documents. Change the declaration name/version when changing an incompatible stored format.

func NewCodec

func NewCodec[V any](encode func(V) ([]byte, error), decode func([]byte) (V, error)) Codec[V]

func (Codec[V]) Validate

func (c Codec[V]) Validate() error

type Config

type Config struct {
	Namespace       Namespace
	MaxKeyBytes     int
	MaxValueBytes   int
	MaxDeclarations int
	MaxBatchEntries int // Maximum batch input keys before deduplication; bounded by MaxBatchEntries.
	MaxTags         int // Maximum references in a tagged view/invalidation; bounded by cache.MaxTags.
	// MaxFills bounds concurrent Remember loaders across this store.
	MaxFills int
	// MaxFillWaiters bounds followers of each Remember loader (excluding its owner).
	MaxFillWaiters int
	// Timeout bounds each backend step (read, write, metadata) and every other
	// operation. It does not cap Remember loaders; LoadTimeout does.
	Timeout time.Duration
	// LoadTimeout bounds one Remember loader run, detached from the requesting
	// caller's cancellation. A call can narrow it with WithLoadTimeout.
	LoadTimeout time.Duration
}

Config bounds one application's typed cache access. Namespace is mandatory. MaxValueBytes bounds encoded data; custom codecs may allocate before that check.

func DefaultConfig

func DefaultConfig(namespace Namespace) Config

func (Config) Validate

func (c Config) Validate() error

type CoordinatedBackend

type CoordinatedBackend interface {
	Backend
	PutLeased(context.Context, EntryKey, []byte, TTL, lease.Proof) error
}

CoordinatedBackend atomically checks a live lease and publishes a value using the same authority as the lease Backend. Validate the proof before I/O, then compare its complete address/owner and finite expiry in the write operation. Missing/replaced/expired ownership returns lease.ErrLost without changing data. Payload validation and ordinary Put semantics still apply. Never retry a write.

type CoordinatedTaggedBackend

type CoordinatedTaggedBackend interface {
	TaggedBackend
	PutTaggedLeased(context.Context, TaggedKey, []byte, TTL, lease.Proof) error
}

CoordinatedTaggedBackend checks both ownership and the tag snapshot atomically with publication. A stale tag returns Conflict and cannot replace newer data.

type CoordinationConfig

type CoordinationConfig struct{ LeaseDuration, Wait time.Duration }

CoordinationConfig sets the renewable fill lease and the maximum time a miss waits while another instance fills (polling both the lease and the cache at the lease manager's PollInterval). Loaders are bounded by LoadTimeout.

func DefaultCoordinationConfig

func DefaultCoordinationConfig() CoordinationConfig

type Counter

type Counter[K any] struct {
	// contains filtered or unexported fields
}

Counter retains its declared key type through exact int64 reads and mutations. It is an evictable cache counter, not durable accounting or a rate limiter. Operations use the Store's namespace, key codec, timeout and error handling. Construct it with CounterDeclaration.Bind; its zero value rejects operations.

func (Counter[K]) Add

func (c Counter[K]) Add(ctx context.Context, key K, value int64, ttl TTL) (bool, error)

Add initializes only an absent or expired counter. Existing value/TTL remain.

func (Counter[K]) Decrement

func (c Counter[K]) Decrement(ctx context.Context, key K, amount int64, initialTTL TTL) (int64, error)

Decrement subtracts a nonnegative amount. Use Increment for a signed delta.

func (Counter[K]) Exists

func (c Counter[K]) Exists(ctx context.Context, key K) (bool, error)

Exists retains the declared counter key type without decoding its value.

func (Counter[K]) Expire

func (c Counter[K]) Expire(ctx context.Context, key K, ttl TTL) (bool, error)

Expire changes the counter's expiry, preserving the existing exact integer.

func (Counter[K]) Forget

func (c Counter[K]) Forget(ctx context.Context, key K) (bool, error)

func (Counter[K]) ForgetMany

func (c Counter[K]) ForgetMany(ctx context.Context, keys ...K) (uint64, error)

ForgetMany atomically removes a bounded batch of distinct counters in this view.

func (Counter[K]) Get

func (c Counter[K]) Get(ctx context.Context, key K) (int64, bool, error)

Get separates a missing entry from a stored zero. Corrupt or noncanonical data returns an error, never a miss or a rounded integer.

func (Counter[K]) Increment

func (c Counter[K]) Increment(ctx context.Context, key K, delta int64, initialTTL TTL) (int64, error)

Increment atomically adds a signed delta and returns the resulting value. A negative delta subtracts. Missing/expired values start at zero with initialTTL; existing entries retain their original expiry, even for delta=0. The initialTTL must always be valid. Overflow/corruption fails without modifying the entry.

func (Counter[K]) Put

func (c Counter[K]) Put(ctx context.Context, key K, value int64, ttl TTL) error

Put replaces the counter and its TTL atomically.

func (Counter[K]) WithTags

func (c Counter[K]) WithTags(first Tag, rest ...Tag) (Counter[K], error)

WithTags retains atomic counter operations while applying the selected tags.

type CounterBackend

type CounterBackend interface {
	Increment(ctx context.Context, key EntryKey, delta int64, initialTTL TTL) (int64, error)
}

CounterBackend is an optional capability of the Backend used by a Store. Increment must be atomic with Get/Put/Add/Forget on the same EntryKey. Its representation is canonical signed decimal int64, shared with Counter values. A missing or expired entry starts at zero; initialTTL applies only to creation. Existing expiry is retained. Corrupt data, overflow and capacity rejection must leave an existing entry unchanged. Invalid initialTTL is always an error. Failed remote writes may still have been applied; do not retry implicitly.

type CounterDeclaration

type CounterDeclaration[K any] struct {
	// contains filtered or unexported fields
}

CounterDeclaration binds one concrete key type to exact signed int64 values. Copies reuse the underlying declaration identity. The zero value is invalid.

func DefineCounter

func DefineCounter[K any](name Name, keys KeyCodec[K]) CounterDeclaration[K]

func (CounterDeclaration[K]) Bind

func (d CounterDeclaration[K]) Bind(store *Store) (Counter[K], error)

Bind validates the counter capability and full int64 byte bound before claiming a family. Value caches and counters share the Store's declaration ownership and capacity checks. Construction performs no adapter operation.

func (CounterDeclaration[K]) Name

func (d CounterDeclaration[K]) Name() Name

func (CounterDeclaration[K]) Validate

func (d CounterDeclaration[K]) Validate() error

type Declaration

type Declaration[K, V any] struct {
	// contains filtered or unexported fields
}

Declaration is an immutable, reusable typed cache family. Its zero value is invalid.

func Define

func Define[K, V any](name Name, keys KeyCodec[K], values Codec[V]) Declaration[K, V]

func (Declaration[K, V]) Bind

func (d Declaration[K, V]) Bind(store *Store) (Cache[K, V], error)

Bind rejects conflicting declarations before they can read each other's data. Copies of the same declaration can bind repeatedly. Separate declarations with the same name fail, including declarations whose payload types happen to match.

func (Declaration[K, V]) Name

func (d Declaration[K, V]) Name() Name

func (Declaration[K, V]) Validate

func (d Declaration[K, V]) Validate() error

type Entry

type Entry[K, V any] struct {
	Key   K
	Value V
}

Entry is one typed key/value pair written by PutMany.

type EntryBackend

type EntryBackend interface {
	Exists(context.Context, EntryKey) (bool, error)
	Expire(context.Context, EntryKey, TTL) (bool, error)
}

EntryBackend inspects existence and updates expiry without materializing payloads. Methods validate the adapter's stored type/envelope/size, but do not run a value codec. Expire returns true for a current entry even if its expiry is unchanged. Missing entries are never created. Context, lifecycle and uncertain-write rules are the same as Backend. No read/write fallback or retry is permitted.

type EntryKey

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

EntryKey is the explicit backend address boundary. Adapters must retain its complete namespace. Applications use their typed Cache to construct addresses.

func NewEntryKey

func NewEntryKey(namespace Namespace, name Name, logical string) (EntryKey, error)

NewEntryKey is intended for adapter authors. Codecs must distinguish semantic keys.

func NewNamespaceTagKey

func NewNamespaceTagKey(namespace Namespace) (EntryKey, error)

NewNamespaceTagKey returns the reserved adapter metadata address for a complete cache namespace. Its feature prefix cannot collide with application cache/tag declarations. Applications call Store.Invalidate instead of managing versions.

func TaggedDataKey

func TaggedDataKey(base EntryKey, tags []EntryKey) (EntryKey, error)

TaggedDataKey returns the stable payload address NewTaggedKey produces for base under the canonical metadata addresses tags, for any versions. Adapters use it to address a combined metadata/payload operation before versions are known.

func (EntryKey) Name

func (k EntryKey) Name() Name

func (EntryKey) Namespace

func (k EntryKey) Namespace() Namespace

func (EntryKey) String

func (k EntryKey) String() string

func (EntryKey) Validate

func (k EntryKey) Validate() error

type Event

type Event struct {
	Family       Name
	Operation    Operation
	Hits, Misses int
	Loaded       bool
	Unpublished  bool
	Stale        bool
	Duration     time.Duration
	Code         fault.Code
}

Event describes one completed typed cache call. Family is the declaration name; it is empty for Store.Invalidate and Store.InvalidateTags. Hits and Misses count the keys a read observed (Get, GetMany, Exists and the lookup inside Remember and Flexible). Loaded reports a miss that ran or joined a loader, and Unpublished that the loaded value could not be stored (the caller still received it). Stale reports a Flexible hit served while a background refresh was requested. Code is the framework classification of a failure and is empty on success. Keys, values and error text are never exposed.

type FlushBackend

type FlushBackend interface {
	FlushNamespace(context.Context, Namespace) (uint64, error)
}

FlushBackend physically removes every entry of one cache namespace. Adapters without tag metadata (file and PostgreSQL) implement it so Store.Invalidate works for every store type. It must not remove other namespaces' entries and must serialize with the adapter's single-entry operations, but it is not a fence: a Remember fill that started earlier can publish afterwards. It returns the number of removed entries; a remote error may hide a partial removal.

type KeyCodec

type KeyCodec[K any] = keyspace.Codec[K]

KeyCodec preserves the declared key type using the shared keyspace codec.

func NewKeyCodec

func NewKeyCodec[K any](encode func(K) (string, error)) KeyCodec[K]

func SignedKeys

func SignedKeys[K ~int | ~int8 | ~int16 | ~int32 | ~int64]() KeyCodec[K]

func StringKeys

func StringKeys[K ~string]() KeyCodec[K]

func TextKeys

func TextKeys[K encoding.TextMarshaler]() KeyCodec[K]

func UnsignedKeys

func UnsignedKeys[K ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr]() KeyCodec[K]

type Lookup

type Lookup[V any] struct {
	Value V
	Found bool
}

Lookup is one typed batch-read result. Found distinguishes a miss from a stored zero value.

type Name

type Name string

Name identifies a declared cache family within an application/environment.

type NamedStore

type NamedStore struct {
	Name  StoreName
	Value *Store
}

NamedStore binds a typed name to an existing borrowed instance.

type Namespace

type Namespace = keyspace.Namespace

Namespace separates applications and environments sharing a backend.

type Observer

type Observer func(context.Context, Event)

Observer receives completed calls synchronously on the calling goroutine. It must be fast, must not use the cache and must not retain the context. A panic is contained and never changes the operation's result.

type Operation

type Operation uint8

Operation identifies a typed cache call reported to an Observer.

const (
	OperationGet Operation = iota + 1
	OperationGetMany
	OperationRemember
	OperationPut
	OperationPutMany
	OperationAdd
	OperationForget
	OperationForgetMany
	OperationIncrement
	OperationExists
	OperationExpire
	OperationInvalidate
	OperationFlexible
)

func (Operation) String

func (o Operation) String() string

String returns a stable snake_case name suitable for metric labels.

type RememberOption

type RememberOption func(*rememberSettings) error

RememberOption narrows one Remember call.

func WithLoadTimeout

func WithLoadTimeout(timeout time.Duration) RememberOption

WithLoadTimeout bounds this call's loader instead of Config.LoadTimeout. The duration must be positive and at most MaxLoadTimeout. Store Config.Timeout never caps a loader; it bounds each backend step around it.

type Requirements

type Requirements struct{ Tags, Counters, Entries, Batches, DistributedFills bool }

Requirements declares capabilities an application's cache declarations need. Assembly checks them before boot instead of failing at the first request.

type SnapshotReadBackend

type SnapshotReadBackend interface {
	ReadSnapshot(ctx context.Context, base EntryKey, tags []EntryKey, payload bool) (TaggedKey, []byte, bool, error)
}

SnapshotReadBackend optionally combines ResolveTags with a tagged read in one atomic operation, halving round trips for Get, Exists and Remember lookups. tags are canonical metadata addresses (see ValidateTagKeys); missing metadata receives fresh random versions exactly as in ResolveTags. The returned key must be NewTaggedKey(base, stamps) for the versions read. found reports a current, readable payload for that key; payload=false skips transferring the value. An obsolete or over-bound stored payload is a miss.

type SnapshotWriteBackend

type SnapshotWriteBackend interface {
	PutSnapshot(ctx context.Context, base EntryKey, tags []EntryKey, data []byte, ttl TTL) error
	AddSnapshot(ctx context.Context, base EntryKey, tags []EntryKey, data []byte, ttl TTL) (bool, error)
	ForgetSnapshot(ctx context.Context, base EntryKey, tags []EntryKey) (bool, error)
	IncrementSnapshot(ctx context.Context, base EntryKey, tags []EntryKey, delta int64, ttl TTL) (int64, error)
	ExpireSnapshot(ctx context.Context, base EntryKey, tags []EntryKey, ttl TTL) (bool, error)
}

SnapshotWriteBackend optionally resolves tag metadata (tags are canonical metadata addresses; missing metadata receives fresh versions exactly as in ResolveTags) and applies one tagged mutation under the resolved snapshot in the same atomic operation. Direct Put, Add, Forget, Increment and Expire then cost one round trip and never cross an invalidation; Remember publications keep the snapshot they read. Results and failures follow the matching TaggedBackend, TaggedCounterBackend and TaggedEntryBackend methods.

type Stats

type Stats struct {
	Hits, Misses, Writes, WriteFailures uint64
	Loads, Coalesced, Uncoalesced       uint64
	SnapshotRetries                     uint64
}

Stats is a snapshot of one Store's typed operation totals since construction. Hits and Misses count Get/Remember reads; Writes counts successful Put, Add and Remember publications; WriteFailures counts Remember publications that failed after a successful load (the caller still received the value). Loads counts Remember loader runs; Coalesced counts callers that joined an existing fill; Uncoalesced counts loads run outside the fill registry because MaxFills was exhausted. SnapshotRetries counts tagged reads that re-resolved a snapshot changed by a concurrent invalidation.

type Store

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

Store binds cache declarations to one namespace and a borrowed Backend. NewStore is pure construction: it performs no I/O and starts no goroutines. Each application builds its own Store and owns the adapter lifecycle. Remember and Flexible run loaders in Store-owned fills; Close stops new operations and drains those fills before the owner closes the backend. TaggedBackend adapters automatically protect every value/counter operation with a reserved namespace generation, enabling Invalidate across stores and processes. FlushBackend adapters invalidate a namespace by physically removing its entries.

func NewCoordinatedStore

func NewCoordinatedStore(manager *lease.Manager, config Config, coordination CoordinationConfig, options ...StoreOption) (*Store, error)

NewCoordinatedStore enables distributed Remember before declarations are bound. The borrowed manager supplies both lease and cache capabilities from the SAME adapter/namespace. It performs no I/O and never falls back to an ordinary store. Close the lease manager before its adapter. Existing Cache methods remain typed.

func NewStore

func NewStore(backend Backend, config Config, options ...StoreOption) (*Store, error)

func (*Store) Close

func (s *Store) Close(ctx context.Context) error

Close rejects new operations with fault.Closed, cancels the contexts of running fills (Remember loaders whose callers have gone, Flexible background refreshes and their publications) and waits for them to exit. ctx bounds only the wait: a callback that ignores cancellation keeps running, and Done closes when it has actually exited. Close never closes the borrowed backend; call it before the backend's owner does. Repeated calls wait again.

func (*Store) Done

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

Done closes after Close once every running fill has exited.

func (*Store) Invalidate

func (s *Store) Invalidate(ctx context.Context) error

Invalidate logically invalidates every typed cache entry in this namespace, including tagged views and counters across Store instances. TaggedBackend stores (memory, Redis) rotate a reserved namespace generation; FlushBackend stores (file, PostgreSQL) physically remove the namespace's entries instead. Basic adapters without either capability return fault.Invalid.

Rotation is atomic. A stale operation cannot publish or delete a newer value; overlapping operations may return fault.Conflict. An in-flight Remember loader retains ownership until it exits, then fails publication if its snapshot changed (its callers still receive the loaded value; the failure is reported). New calls use independent fill identities. No loader or mutation is retried. A physical flush is not a fence: a fill that started earlier may publish after it.

This is not physical erasure for tagged stores: stale payloads expire or are reclaimed on access, replacement or adapter eviction, using stable addresses even for Forever data. Tag metadata, leases, rate limits, pub/sub and other namespaces are not cleared. Direct adapter writes bypass this typed-store contract. A remote error may hide an applied rotation; it does not prove that invalidation was rolled back.

func (*Store) InvalidateTags

func (s *Store) InvalidateTags(ctx context.Context, first Tag, rest ...Tag) error

InvalidateTags atomically invalidates entries containing any selected reference. A remote error may hide an applied invalidation; no implicit retry occurs.

func (*Store) Namespace

func (s *Store) Namespace() Namespace

func (*Store) Require

func (s *Store) Require(required Requirements) error

func (*Store) Stats

func (s *Store) Stats() Stats

type StoreName

type StoreName string

StoreName identifies one configured cache instance, distinct from other service families.

func (StoreName) Validate

func (n StoreName) Validate() error

type StoreOption

type StoreOption func(*Store) error

StoreOption configures optional Store collaborators at construction.

func WithLogger

func WithLogger(logger *slog.Logger) StoreOption

WithLogger borrows a structured logger for cache failures that do not fail the caller, such as a Remember whose loaded value could not be published. Records carry the cache family and a redacted errordiag diagnostic, never keys, values or arbitrary error text. Application assembly supplies its configured logger.

func WithObserver

func WithObserver(observer Observer) StoreOption

WithObserver installs one call observer for metrics or tracing. Observers are fixed for the Store's lifetime.

type Stores

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

Stores is immutable. Default and named access alias the same owned instance. Resolve dependencies during construction; pass concrete services to domain code.

func NewStores

func NewStores(selected StoreName, entries ...NamedStore) (*Stores, error)

func (*Stores) Default

func (r *Stores) Default() (*Store, error)

func (*Stores) DefaultName

func (r *Stores) DefaultName() StoreName

func (*Stores) Names

func (r *Stores) Names() []StoreName

func (*Stores) Store

func (r *Stores) Store(name StoreName) (*Store, error)

type TTL

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

TTL explicitly selects finite expiry or persistence. Its zero value is invalid. A TTL takes effect when the backend accepts a write, not when its value is encoded.

func For

func For(duration time.Duration) TTL

func Forever

func Forever() TTL

func (TTL) Duration

func (t TTL) Duration() time.Duration

func (TTL) IsForever

func (t TTL) IsForever() bool

func (TTL) Validate

func (t TTL) Validate() error

type Tag

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

Tag is an opaque bound reference. The key is encoded during an operation, under its context and error boundary. Keep reference-backed key values immutable.

type TagDeclaration

type TagDeclaration[K any] struct {
	// contains filtered or unexported fields
}

TagDeclaration owns a tag family's concrete key type. It shares declaration ownership and limits with value/counter families within the same Store.

func DefineTag

func DefineTag[K any](name Name, keys KeyCodec[K]) TagDeclaration[K]

func (TagDeclaration[K]) Bind

func (d TagDeclaration[K]) Bind(store *Store) (Tags[K], error)

func (TagDeclaration[K]) Name

func (d TagDeclaration[K]) Name() Name

func (TagDeclaration[K]) Validate

func (d TagDeclaration[K]) Validate() error

type TagStamp

type TagStamp struct {
	Key     EntryKey
	Version TagVersion
}

TagStamp is the adapter boundary for a resolved tag identity and version.

type TagVersion

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

TagVersion is an opaque random identity. Missing metadata must get a fresh version; recreating a previous/default version could resurrect stale values.

func NewTagVersion

func NewTagVersion() (TagVersion, error)

func ParseTagVersion

func ParseTagVersion(data []byte) (TagVersion, error)

func (TagVersion) Bytes

func (v TagVersion) Bytes() []byte

func (TagVersion) Validate

func (v TagVersion) Validate() error

type TaggedBackend

type TaggedBackend interface {
	ResolveTags(context.Context, []EntryKey) ([]TagVersion, error)
	InvalidateTags(context.Context, []EntryKey) error
	GetTagged(context.Context, TaggedKey) ([]byte, bool, error)
	PutTagged(context.Context, TaggedKey, []byte, TTL) error
	AddTagged(context.Context, TaggedKey, []byte, TTL) (bool, error)
	ForgetTagged(context.Context, TaggedKey) (bool, error)
}

TaggedBackend adds atomic tag metadata and conditional tagged entry operations to a Backend. ResolveTags returns versions in input order, creating fresh random versions for missing metadata. Inputs are canonical and bounded; adapters must still validate their public boundary. InvalidateTags replaces all selected versions atomically. Missing metadata never reuses an earlier/default version.

Tagged operations must reject a changed/missing snapshot with fault.Conflict before altering data. Get/Add treat an older stored fingerprint as absent; a conditional cleanup cannot delete a newer replacement. Storage addresses stay stable across versions. Size, ownership and corruption failures preserve live entries. All methods must observe context and share Backend lifecycle/limits.

type TaggedBatchBackend

type TaggedBatchBackend interface {
	ForgetManyTagged(context.Context, []TaggedKey) (uint64, error)
}

TaggedBatchBackend additionally requires every entry to share one tag snapshot. Missing/changed metadata returns Conflict before deletion. Old stored generations are logically absent and do not increase the live removal count. Adapters share ValidateTaggedBatch; metadata and unrelated keys must remain untouched.

type TaggedBatchReadBackend

type TaggedBatchReadBackend interface {
	GetManyTagged(context.Context, []TaggedKey) ([]BatchValue, error)
}

TaggedBatchReadBackend reads a canonical batch sharing one tag snapshot (see ValidateTaggedBatch) atomically with its validation. A changed or missing snapshot returns Conflict; obsolete payloads are misses and may be reclaimed.

type TaggedCounterBackend

type TaggedCounterBackend interface {
	IncrementTagged(context.Context, TaggedKey, int64, TTL) (int64, error)
}

TaggedCounterBackend is the optional exact counter capability for tagged views. It combines snapshot validation and Increment semantics in one atomic operation.

type TaggedEntryBackend

type TaggedEntryBackend interface {
	ExistsTagged(context.Context, TaggedKey) (bool, error)
	ExpireTagged(context.Context, TaggedKey, TTL) (bool, error)
}

TaggedEntryBackend checks a current tag snapshot atomically with inspection or expiry mutation. Stale callers return Conflict; obsolete stored entries are logically absent and may be reclaimed, without decoding their old payloads.

type TaggedKey

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

TaggedKey owns an immutable canonical snapshot. Its data address is stable across invalidations; its fill address includes versions for Remember isolation.

func NewTaggedKey

func NewTaggedKey(base EntryKey, stamps []TagStamp) (TaggedKey, error)

NewTaggedKey is for adapters. Applications construct typed tags and WithTags views. Duplicate keys with conflicting versions and cross-namespace tags fail.

func (TaggedKey) DataKey

func (k TaggedKey) DataKey() EntryKey

func (TaggedKey) FillKey

func (k TaggedKey) FillKey() EntryKey

func (TaggedKey) Fingerprint

func (k TaggedKey) Fingerprint() [sha256.Size]byte

func (TaggedKey) Stamps

func (k TaggedKey) Stamps() []TagStamp

func (TaggedKey) Validate

func (k TaggedKey) Validate() error

func (TaggedKey) VersionFingerprint

func (k TaggedKey) VersionFingerprint() []byte

VersionFingerprint is the canonical concatenation of the snapshot's versions (TagVersionBytes each, in Stamps order). The data address already fixes the tag identities, so this identifies the snapshot exactly. Adapters whose authority cannot compute SHA-256 (such as Redis Lua) persist this instead of Fingerprint, which lets one atomic operation resolve metadata and compare.

type Tags

type Tags[K any] struct {
	// contains filtered or unexported fields
}

Tags retains the declared key type when creating references or invalidating.

func (Tags[K]) For

func (t Tags[K]) For(key K) Tag

func (Tags[K]) Invalidate

func (t Tags[K]) Invalidate(ctx context.Context, key K) error

Directories

Path Synopsis
Package file implements a bounded persistent cache under an owned os.Root.
Package file implements a bounded persistent cache under an owned os.Root.
Package memory supplies a bounded, concurrency-safe cache backend for local development and deterministic tests.
Package memory supplies a bounded, concurrency-safe cache backend for local development and deterministic tests.
Package null supplies a cache backend that retains nothing, for disabling a cache store without changing its typed declarations.
Package null supplies a cache backend that retains nothing, for disabling a cache store without changing its typed declarations.
Package postgres implements persistent cache using an explicitly migrated PostgreSQL table and borrowed Foundry connection.
Package postgres implements persistent cache using an explicitly migrated PostgreSQL table and borrowed Foundry connection.

Jump to

Keyboard shortcuts

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