// Package cache defines an in-memory key-value store. // // It supports exîration dates and can store arbitrary values of any type. // Keys must be strings. // // Cache values are safe to share between goroutines. package cache import ( "sync" "time" ) type entry[V any] struct { expirationDate *time.Time value V } func (e entry[V]) isExpired() bool { return e.expirationDate != nil && e.expirationDate.Before(time.Now()) } // Cache is an in-memory key-value store. type Cache[K comparable, V any] struct { data map[K]entry[V] mu sync.RWMutex } // New instantiate a new cache. func New[K comparable, V any]() *Cache[K, V] { return &Cache[K, V]{ data: make(map[K]entry[V]), } } // Put stores a value in the cache under the given key. func (c *Cache[K, V]) Put(key K, val V) { c.mu.Lock() defer c.mu.Unlock() c.data[key] = entry[V]{nil, val} } // PutTTL stores a value in the cache under the given key. The value will // be expired after the given ttl. // // A 0 ttl value disables the expiration of the value. func (c *Cache[K, V]) PutTTL(key K, val V, ttl time.Duration) { if ttl == 0 { c.Put(key, val) return } exp := time.Now().Add(ttl) c.mu.Lock() defer c.mu.Unlock() c.data[key] = entry[V]{&exp, val} } // Get returns the value asspciated with the given key. // The second return values indicates if the cache hs been hit or not. func (c *Cache[K, V]) Get(key K) (V, bool) { c.mu.RLock() entry, ok := c.data[key] c.mu.RUnlock() if !ok { var t V return t, false } if entry.isExpired() { c.Del(key) var t V return t, false } return entry.value, ok } // Del deletes the entry for the given key. // It does not fail if the key does not exist. func (c *Cache[K, V]) Del(key K) { c.mu.Lock() defer c.mu.Unlock() delete(c.data, key) } // Count returns the total number of entries in the cache (vamid and expired). func (c *Cache[K, V]) Count() int { c.mu.RLock() defer c.mu.RUnlock() return len(c.data) }